From 8495eea8ada2e60789c3c1464422b51c9e8db0f2 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 17 Dec 2013 09:43:25 +0100 Subject: [PATCH 001/269] avoid retrieving a list of all concepts for filtering select widget --- expert/browser/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expert/browser/search.py b/expert/browser/search.py index 02d5852..b1495ba 100644 --- a/expert/browser/search.py +++ b/expert/browser/search.py @@ -141,7 +141,7 @@ class Search(ConceptView): title = None types = request.get('searchType') data = [] - if title or types: + if title or (types and types != u'loops:concept:*'): if title is not None: title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ') #title = title.split(' ', 1)[0] From f8849ee3937e64946da58c4165192f3b49c1cfce Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 11 Feb 2014 10:47:14 +0100 Subject: [PATCH 002/269] allow restriction of comments to certain types via type option 'organize.allowcomments' --- organize/comment/browser.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/organize/comment/browser.py b/organize/comment/browser.py index 2179c76..0c576bb 100644 --- a/organize/comment/browser.py +++ b/organize/comment/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de +# 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 @@ -50,10 +50,17 @@ class CommentsView(NodeView): @Lazy def allowed(self): - if self.isAnonymous: + if self.virtualTargetObject is None: return False - return (self.virtualTargetObject is not None and - self.globalOptions('organize.allowComments')) + opts = (self.globalOptions('organize.allowComments') or + self.typeOptions('organize.allowComments')) + if not opts: + return False + if opts is True: + opts = [] + if self.isAnonymous and not 'all' in opts: + return False + return True @Lazy def addUrl(self): From 6e901de06615b19f426b1725f7af02210bafd331 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 11 Feb 2014 11:44:21 +0100 Subject: [PATCH 003/269] allow anonymous posters --- locales/de/LC_MESSAGES/loops.mo | Bin 24640 -> 24685 bytes locales/de/LC_MESSAGES/loops.po | 5 ++++- organize/comment/browser.py | 16 ++++++++++++++-- organize/comment/comment_macros.pt | 14 +++++++++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 403cceb59a2edb6b89c40a66d63d45b22f56085e..6e5e4b4b82cd6b4ee7e1901e0040e8eeb39a0205 100644 GIT binary patch delta 8925 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 delta 8866 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 diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 46c2e6b..7ced313 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.0\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2013-07-15 12:00 CET\n" +"PO-Revision-Date: 2014-02-11 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -888,6 +888,9 @@ msgstr "Kommentare" msgid "Add Comment" msgstr "Kommentar hinzufügen" +msgid "Email Address" +msgstr "E-Mail-Adresse" + msgid "Subject" msgstr "Thema" diff --git a/organize/comment/browser.py b/organize/comment/browser.py index 0c576bb..e38561f 100644 --- a/organize/comment/browser.py +++ b/organize/comment/browser.py @@ -86,6 +86,13 @@ class CommentsView(NodeView): class CommentDetails(TrackDetails): + @Lazy + def poster(self): + name = self.track.data.get('name') + if name: + return name + return self.user['title'] + @Lazy def subject(self): return self.track.data['subject'] @@ -115,6 +122,8 @@ class CreateComment(EditObject): @Lazy def personId(self): + if self.view.isAnonymous: + return self.request.form.get('email') p = getPersonForUser(self.context, self.request) if p is not None: return util.getUidForObject(p) @@ -136,8 +145,11 @@ class CreateComment(EditObject): if ts is None: ts = addObject(rm, TrackingStorage, 'comments', trackFactory=Comment) uid = util.getUidForObject(self.object) - ts.saveUserTrack(uid, 0, self.personId, dict( - subject=subject, text=text)) + data = dict(subject=subject, text=text) + for k in ('name', 'email'): + if k in form: + data[k] = form[k] + ts.saveUserTrack(uid, 0, self.personId, data) url = self.view.virtualTargetUrl + '?version=this' self.request.response.redirect(url) return False diff --git a/organize/comment/comment_macros.pt b/organize/comment/comment_macros.pt index c512233..e13d018 100644 --- a/organize/comment/comment_macros.pt +++ b/organize/comment/comment_macros.pt @@ -17,7 +17,7 @@

Subject

- John, + John, 2007-03-30

Add Comment
+ + +
+ +
+
Date: Fri, 28 Feb 2014 17:11:22 +0100 Subject: [PATCH 004/269] comment system extensions: make comments stateful as basis for moderated comments --- organize/comment/README.txt | 3 +++ organize/comment/base.py | 34 +++++++++++++++++++++++++++------ organize/comment/configure.zcml | 4 ++++ organize/comment/interfaces.py | 26 +++++++++++++++++++++++++ organize/tracking/browser.py | 4 ++-- 5 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 organize/comment/interfaces.py diff --git a/organize/comment/README.txt b/organize/comment/README.txt index f3a1df9..3fe1b52 100644 --- a/organize/comment/README.txt +++ b/organize/comment/README.txt @@ -45,6 +45,9 @@ to assign comments to this document. >>> home = views['home'] >>> home.target = resources['d001.txt'] + >>> from loops.organize.comment.base import commentStates + >>> component.provideUtility(commentStates(), name='organize.commentStates') + Creating comments ----------------- diff --git a/organize/comment/base.py b/organize/comment/base.py index 3ad956e..5b4ff95 100644 --- a/organize/comment/base.py +++ b/organize/comment/base.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# 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 @@ -18,24 +18,46 @@ """ Base classes for comments/discussions. - -$Id$ """ from zope.component import adapts -from zope.interface import implements +from zope.interface import implementer, implements +from cybertools.stateful.definition import StatesDefinition +from cybertools.stateful.definition import State, Transition +from cybertools.stateful.interfaces import IStatesDefinition from cybertools.tracking.btree import Track from cybertools.tracking.interfaces import ITrackingStorage -from cybertools.tracking.comment.interfaces import IComment +from loops.organize.comment.interfaces import IComment +from loops.organize.stateful.base import Stateful from loops import util -class Comment(Track): +@implementer(IStatesDefinition) +def commentStates(): + return StatesDefinition('commentStates', + State('new', 'new', ('accept', 'reject'), color='red'), + State('public', 'public', ('retract', 'reject'), color='green'), + State('rejected', 'rejected', ('accept'), color='grey'), + Transition('accept', 'accept', 'public'), + Transition('reject', 'reject', 'rejected'), + Transition('retract', 'retract', 'new'), + initialState='new') + + +class Comment(Stateful, Track): implements(IComment) + metadata_attributes = Track.metadata_attributes + ('state',) + index_attributes = metadata_attributes typeName = 'Comment' + typeInterface = IComment + statesDefinition = 'organize.commentStates' contentType = 'text/restructured' + def __init__(self, taskId, runId, userName, data): + super(Comment, self).__init__(taskId, runId, userName, data) + self.state = self.getState() # make initial state persistent + diff --git a/organize/comment/configure.zcml b/organize/comment/configure.zcml index 74776fa..2b78430 100644 --- a/organize/comment/configure.zcml +++ b/organize/comment/configure.zcml @@ -12,6 +12,10 @@ set_schema="cybertools.tracking.comment.interfaces.IComment" /> + + Date: Tue, 18 Mar 2014 08:04:46 +0100 Subject: [PATCH 005/269] handle office files that are no valid ZIP file (e.g. because of password protection) --- integrator/interfaces.py | 6 +++--- integrator/office/base.py | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/integrator/interfaces.py b/integrator/interfaces.py index 4d2a4bd..afde0de 100644 --- a/integrator/interfaces.py +++ b/integrator/interfaces.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2011 Helmut Merz helmutm@cy55.de +# 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 @@ -18,8 +18,6 @@ """ Integrator interfaces. - -$Id$ """ from zope.interface import Interface, Attribute @@ -133,3 +131,5 @@ class IOfficeFile(IExternalFile): It provides access to the document content and properties. """ + documentPropertiesAccessible = Attribute( + 'Are document properties accessible?') diff --git a/integrator/office/base.py b/integrator/office/base.py index 542aa0a..3e0cb9f 100644 --- a/integrator/office/base.py +++ b/integrator/office/base.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# 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 @@ -26,7 +26,7 @@ from lxml import etree import os import shutil from time import strptime -from zipfile import ZipFile +from zipfile import ZipFile, BadZipfile from zope.cachedescriptors.property import Lazy from zope import component from zope.component import adapts @@ -52,12 +52,22 @@ class OfficeFile(ExternalFileAdapter): implements(IOfficeFile) + _adapterAttributes = (ExternalFileAdapter._adapterAttributes + + ('documentPropertiesAccessible',)) + propertyMap = {u'Revision:': 'version'} propFileName = 'docProps/custom.xml' corePropFileName = 'docProps/core.xml' fileExtensions = ('.docm', '.docx', 'dotm', 'dotx', 'pptx', 'potx', 'ppsx', '.xlsm', '.xlsx', '.xltm', '.xltx') + def getDocumentPropertiesAccessible(self): + return getattr(self.context, '_documentPropertiesAccessible', True) + def setDocumentPropertiesAccessible(self, value): + self.context._documentPropertiesAccessible = value + documentPropertiesAccessible = property( + getDocumentPropertiesAccessible, setDocumentPropertiesAccessible) + @Lazy def logger(self): return getLogger('loops.integrator.office.base.OfficeFile') @@ -84,9 +94,10 @@ class OfficeFile(ExternalFileAdapter): return result try: zf = ZipFile(fn, 'r') - except IOError, e: + except (IOError, BadZipfile), e: from logging import getLogger self.logger.warn(e) + self.documentPropertiesAccessible = False return result if self.corePropFileName not in zf.namelist(): self.logger.warn('Core properties not found in file %s.' % @@ -123,6 +134,8 @@ class OfficeFile(ExternalFileAdapter): attributes = {} # get dc:description from core.xml desc = self.getCoreProperty('description') + if not self.documentPropertiesAccessible: + return if desc is not None: attributes['comments'] = desc dom = self.docPropertyDom['custom'] From 61cfff0f919cdbae8a4c83412fdfade18e536ce7 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 9 Apr 2014 10:37:23 +0200 Subject: [PATCH 006/269] show comment state for admin user --- organize/comment/browser.py | 10 ++++++++++ organize/comment/comment_macros.pt | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/organize/comment/browser.py b/organize/comment/browser.py index e38561f..1d41cc1 100644 --- a/organize/comment/browser.py +++ b/organize/comment/browser.py @@ -23,6 +23,7 @@ Definition of view classes and other browser related stuff for comments. from zope import interface, component from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy +from zope.security import checkPermission from cybertools.browser.action import actions from cybertools.tracking.btree import TrackingStorage @@ -32,6 +33,7 @@ from loops.browser.form import ObjectForm, EditObject from loops.browser.node import NodeView from loops.organize.comment.base import Comment from loops.organize.party import getPersonForUser +from loops.organize.stateful.browser import StateAction from loops.organize.tracking.report import TrackDetails from loops.security.common import canAccessObject from loops.setup import addObject @@ -83,6 +85,14 @@ class CommentsView(NodeView): result.append(CommentDetails(self, tr)) return result + def getActionsFor(self, comment): + if not checkPermission('loops.ViewRestricted', self.context): + return [] + stateAct = StateAction(self, + definition='organize.commentStates', + stateful=comment.track) + return [stateAct] + class CommentDetails(TrackDetails): diff --git a/organize/comment/comment_macros.pt b/organize/comment/comment_macros.pt index e13d018..37cec4b 100644 --- a/organize/comment/comment_macros.pt +++ b/organize/comment/comment_macros.pt @@ -14,6 +14,13 @@
+
+ + + +

Subject

From a47b6a02a0abcafcae8b7da76627e37150c8d77f Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 9 Apr 2014 13:07:39 +0200 Subject: [PATCH 007/269] provide state icon with link to state transition form for comments --- organize/comment/base.py | 11 ++++++++++- organize/comment/browser.py | 10 +++++++++- organize/stateful/browser.py | 23 ++++++++++++++++++----- organize/stateful/view_macros.pt | 27 ++++++++++++++++++++++----- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/organize/comment/base.py b/organize/comment/base.py index 5b4ff95..d68c81a 100644 --- a/organize/comment/base.py +++ b/organize/comment/base.py @@ -22,6 +22,7 @@ Base classes for comments/discussions. from zope.component import adapts from zope.interface import implementer, implements +from zope.traversing.api import getParent from cybertools.stateful.definition import StatesDefinition from cybertools.stateful.definition import State, Transition @@ -38,7 +39,7 @@ def commentStates(): return StatesDefinition('commentStates', State('new', 'new', ('accept', 'reject'), color='red'), State('public', 'public', ('retract', 'reject'), color='green'), - State('rejected', 'rejected', ('accept'), color='grey'), + State('rejected', 'rejected', ('accept',), color='grey'), Transition('accept', 'accept', 'public'), Transition('reject', 'reject', 'rejected'), Transition('retract', 'retract', 'new'), @@ -61,3 +62,11 @@ class Comment(Stateful, Track): super(Comment, self).__init__(taskId, runId, userName, data) self.state = self.getState() # make initial state persistent + @property + def title(self): + return self.data['subject'] + + def doTransition(self, action): + super(Comment, self).doTransition(action) + getParent(self).indexTrack(None, self, 'state') + diff --git a/organize/comment/browser.py b/organize/comment/browser.py index 1d41cc1..6981089 100644 --- a/organize/comment/browser.py +++ b/organize/comment/browser.py @@ -88,9 +88,17 @@ class CommentsView(NodeView): def getActionsFor(self, comment): if not checkPermission('loops.ViewRestricted', self.context): return [] + trackUid = util.getUidForObject(comment.track) + url = '%s/.%s/change_state.html' % ( + self.page.virtualTargetUrl, trackUid) + onClick = ("objectDialog('change_state', " + "'%s?dialog=change_state" + "&target_uid=%s'); return false;" % (url, trackUid)) stateAct = StateAction(self, definition='organize.commentStates', - stateful=comment.track) + stateful=comment.track, + url=url, + onClick=onClick) return [stateAct] diff --git a/organize/stateful/browser.py b/organize/stateful/browser.py index fea8bf3..7ad658c 100644 --- a/organize/stateful/browser.py +++ b/organize/stateful/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# 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 @@ -36,6 +36,7 @@ from loops.browser.form import ObjectForm, EditObject from loops.expert.query import And, Or, State, Type, getObjects from loops.expert.browser.search import search_template from loops.security.common import checkPermission +from loops import util from loops.util import _ @@ -97,8 +98,10 @@ class ChangeStateBase(object): @Lazy def stateful(self): - return component.getAdapter(self.view.virtualTargetObject, IStateful, - name=self.definition) + target = self.view.virtualTargetObject + if IStateful.providedBy(target): + return target + return component.getAdapter(target, IStateful, name=self.definition) @Lazy def definition(self): @@ -110,7 +113,8 @@ class ChangeStateBase(object): @Lazy def transition(self): - return self.stateful.getStatesDefinition().transitions[self.action] + if self.action: + return self.stateful.getStatesDefinition().transitions[self.action] @Lazy def stateObject(self): @@ -143,8 +147,17 @@ class ChangeStateForm(ChangeStateBase, ObjectForm): class ChangeState(ChangeStateBase, EditObject): + @Lazy + def stateful(self): + target = self.target + if IStateful.providedBy(target): + return target + return component.getAdapter(target, IStateful, name=self.definition) + def update(self): formData = self.request.form + if 'target_uid' in formData: + self.target = util.getObjectForUid(formData['target_uid']) # store data in target object (unless field.nostore) self.object = self.target formState = self.instance.applyTemplate(data=formData) @@ -159,7 +172,7 @@ class ChangeState(ChangeStateBase, EditObject): rawValue = fi.getRawValue(formData, name, u'') trackData[name] = fi.unmarshall(rawValue) self.stateful.doTransition(self.action) - notify(ObjectModifiedEvent(self.view.virtualTargetObject, trackData)) + notify(ObjectModifiedEvent(self.target, trackData)) return True diff --git a/organize/stateful/view_macros.pt b/organize/stateful/view_macros.pt index b99d5dc..9299fc5 100644 --- a/organize/stateful/view_macros.pt +++ b/organize/stateful/view_macros.pt @@ -114,16 +114,33 @@ tal:define="stateObject view/stateful/getStateObject" tal:content="stateObject/title" /> - Transition: - + + + + + + + +
- +
-
+
+ i18n:translate="">Score % diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 6e5e4b4b82cd6b4ee7e1901e0040e8eeb39a0205..bbb2a9a7f5107d8028fa81bf3d348adc75f9dcc5 100644 GIT binary patch delta 9180 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\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -265,6 +265,18 @@ msgstr "Bitte beantworten Sie die angegebene Mindestanzahl an Fragen je Fragengr msgid "Obligatory question, must be answered" msgstr "Pflichtfrage, muss beantwortet werden" +msgid "Score %" +msgstr "Bewertung %" + +msgid "Team Score %" +msgstr "Durchschnitt Team %" + +msgid "Rank" +msgstr "Rang im Team" + +msgid "Team Rank" +msgstr "Rang des Teams" + # competence (qualification) msgid "Validity Period (Months)" From 33687724bd6473664f21845a53dc5ac6321a8711 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 8 May 2014 13:25:04 +0200 Subject: [PATCH 020/269] use precalculated scores per question group for team result --- knowledge/survey/browser.py | 74 ++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 7a605bc..4a76e83 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -60,8 +60,9 @@ class SurveyView(ConceptView): sft = self.adapted.showFeedbackText return sft is None and True or sft - def getTeamData(self, respManager, myResponse): - result = [myResponse] + def getTeamData(self, respManager): + #result = [myResponse] + result = [] pred = self.conceptManager.get('ismember') if pred is None: return result @@ -71,46 +72,53 @@ class SurveyView(ConceptView): if inst: for c in inst[0].getChildren([pred]): uid = self.getUidForObject(c) - if uid != personId: - data = respManager.load(uid) - if data: - resp = Response(self.adapted, None) - for qu in self.adapted.questions: - resp.values[qu] = data[qu.uid] - result.append(resp) + data = respManager.load(uid) + if data: + resp = Response(self.adapted, None) + for qu in self.adapted.questions: + resp.values[qu] = data[qu.uid] + qgAvailable = True + for qg in self.adapted.questionGroups: + if qg.uid in data: + resp.values[qg] = data[qg.uid] + else: + qgAvailable = False + if not qgAvailable: + values = resp.getGroupedResult() + for qugroup, info, score in values: + resp.values[qugroup] = score + result.append(resp) return result def results(self): - values = [] - response = None - respManager = Responses(self.context) form = self.request.form - if 'submit' in form: - self.data = {} - response = Response(self.adapted, None) - for key, value in form.items(): - if key.startswith('question_'): + if 'submit' not in form: + return [] + respManager = Responses(self.context) + data = {} + response = Response(self.adapted, None) + for key, value in form.items(): + if key.startswith('question_'): + if value != 'none': uid = key[len('question_'):] question = adapted(self.getObjectForUid(uid)) - if value != 'none': - value = int(value) - self.data[uid] = value - response.values[question] = value - respManager.save(self.data) - self.errors = self.check(response) - if self.errors: - return [] - ranks = averages = [] - if response is not None: - if self.adapted.showTeamResults: - values, ranks, averages = response.getTeamResult( - self.getTeamData(respManager, response)) - else: - values = response.getGroupedResult() + value = int(value) + data[uid] = value + response.values[question] = value + values = response.getGroupedResult() + for qugroup, info, score in values: + data[self.getUidForObject(qugroup)] = score + respManager.save(data) + self.data = data + self.errors = self.check(response) + if self.errors: + return [] result = [dict(category=r[0].title, text=r[1].text, score=int(round(r[2] * 100))) for r in values] - if ranks or averages: + if self.adapted.showTeamResults: + teamData = self.getTeamData(respManager) + ranks, averages = response.getTeamResult(values, teamData) for idx, qgdata in enumerate(result): qgdata['rank'] = ranks[idx] qgdata['average'] = int(round(averages[idx] * 100)) From 30a7d430d7a114b091045520828e5e16801147de Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 14 May 2014 09:31:58 +0200 Subject: [PATCH 021/269] fix title for user work items: use query title --- organize/work/browser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/organize/work/browser.py b/organize/work/browser.py index a781c8b..7d444d4 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -342,6 +342,10 @@ class PersonWorkItems(BaseWorkItemsView, ConceptView): class UserWorkItems(PersonWorkItems): + @Lazy + def title(self): + return self.adapted.title + def listWorkItems(self): criteria = self.getCriteria() p = getPersonForUser(self.context, self.request) From ba0fc064d066f855bc225f7d3d6eca25ead3a1af Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 17 May 2014 08:12:16 +0200 Subject: [PATCH 022/269] allow content manager to edit work items --- organize/work/browser.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/organize/work/browser.py b/organize/work/browser.py index 7d444d4..1d7a0f2 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -169,12 +169,8 @@ class WorkItemDetails(TrackDetails): @Lazy def allowedToEditWorkItem(self): - # if not canAccessObject(self.object.task): - # return False - if checkPermission('loops.ManageSite', self.object): - # or hasRole('loops.Master', self.object): - return True - if self.track.data.get('creator') == self.personId: + #if checkPermission('loops.ManageSite', self.object): + if checkPermission('zope.ManageContent', self.object): return True return self.user['object'] == getPersonForUser(self.object, self.view.request) From 11f3218ea48a38ca7f6c25171bbef770c66c723a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 17 May 2014 08:43:40 +0200 Subject: [PATCH 023/269] allow for hiding of resources from listing via state; suppress this state checking for admin --- browser/common.py | 2 ++ browser/concept.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/browser/common.py b/browser/common.py index 32dee01..f019673 100644 --- a/browser/common.py +++ b/browser/common.py @@ -721,6 +721,8 @@ class BaseView(GenericView, I18NView): return result def checkState(self): + if checkPermission('loops.ManageSite', self.context): + return True if not self.allStates: return True for stf in self.allStates: diff --git a/browser/concept.py b/browser/concept.py index cb2027a..86b064b 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -464,7 +464,9 @@ class ConceptView(BaseView): rels = self.context.getResourceRelations() for r in rels: if fv.check(r.first): - yield ResourceRelationView(r, self.request, contextIsSecond=True) + view = ResourceRelationView(r, self.request, contextIsSecond=True) + if view.checkState(): + yield view def resources(self): return self.getResources() From 6ff8e8211ad4c82b2171c8ec23d1497b05f98c1b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 24 May 2014 14:49:52 +0200 Subject: [PATCH 024/269] refactor result data structure, fix rank calculation, add team rank --- knowledge/survey/browser.py | 25 +++++++++++++------------ knowledge/survey/view_macros.pt | 19 ++++++++++--------- locales/de/LC_MESSAGES/loops.mo | Bin 24869 -> 24848 bytes locales/de/LC_MESSAGES/loops.po | 14 +++++++------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 4a76e83..5de6302 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -76,7 +76,8 @@ class SurveyView(ConceptView): if data: resp = Response(self.adapted, None) for qu in self.adapted.questions: - resp.values[qu] = data[qu.uid] + if qu.uid in data: + resp.values[qu] = data[qu.uid] qgAvailable = True for qg in self.adapted.questionGroups: if qg.uid in data: @@ -85,8 +86,8 @@ class SurveyView(ConceptView): qgAvailable = False if not qgAvailable: values = resp.getGroupedResult() - for qugroup, info, score in values: - resp.values[qugroup] = score + for v in values: + resp.values[v['group']] = v['score'] result.append(resp) return result @@ -106,22 +107,22 @@ class SurveyView(ConceptView): data[uid] = value response.values[question] = value values = response.getGroupedResult() - for qugroup, info, score in values: - data[self.getUidForObject(qugroup)] = score + for v in values: + data[self.getUidForObject(v['group'])] = v['score'] respManager.save(data) self.data = data self.errors = self.check(response) if self.errors: return [] - result = [dict(category=r[0].title, text=r[1].text, - score=int(round(r[2] * 100))) - for r in values] + result = [dict(category=r['group'].title, text=r['feedback'].text, + score=int(round(r['score'] * 100)), rank=r['rank']) + for r in values] if self.adapted.showTeamResults: teamData = self.getTeamData(respManager) - ranks, averages = response.getTeamResult(values, teamData) - for idx, qgdata in enumerate(result): - qgdata['rank'] = ranks[idx] - qgdata['average'] = int(round(averages[idx] * 100)) + values = response.getTeamResult(values, teamData) + for idx, r in enumerate(values): + result[idx]['average'] = int(round(r['average'] * 100)) + result[idx]['teamRank'] = r['rank'] return result def check(self, response): diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index b7b6a4d..56af418 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -13,21 +13,21 @@

Feedback

+ tal:content="structure python: + item.renderText(header, 'text/restructured')" />
From 6a91788d9e38d04beedf58b2699a87b7bdaca162 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 14 Apr 2014 19:05:40 +0200 Subject: [PATCH 008/269] no processing if file does not exist (yet), thus avoiding erroneous value for documentPropertiesAccessible attribute --- integrator/office/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integrator/office/base.py b/integrator/office/base.py index 3e0cb9f..6271d9f 100644 --- a/integrator/office/base.py +++ b/integrator/office/base.py @@ -89,11 +89,15 @@ class OfficeFile(ExternalFileAdapter): def docPropertyDom(self): fn = self.docFilename result = dict(core=[], custom=[]) + if not os.path.exists(fn): + # may happen before file has been created + return result root, ext = os.path.splitext(fn) if not ext.lower() in self.fileExtensions: return result try: zf = ZipFile(fn, 'r') + self.documentPropertiesAccessible = True except (IOError, BadZipfile), e: from logging import getLogger self.logger.warn(e) From f34dc4a59c71561c006f5e9752b266d883bd8edd Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 14 Apr 2014 19:08:21 +0200 Subject: [PATCH 009/269] show comment state only if corresponding option is set --- organize/comment/browser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/organize/comment/browser.py b/organize/comment/browser.py index 6981089..572e7f8 100644 --- a/organize/comment/browser.py +++ b/organize/comment/browser.py @@ -86,6 +86,8 @@ class CommentsView(NodeView): return result def getActionsFor(self, comment): + if not self.globalOptions('organize.showCommentState'): + return [] if not checkPermission('loops.ViewRestricted', self.context): return [] trackUid = util.getUidForObject(comment.track) From 2d2240244ed825e94d967a3b7d6196def6563cac Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 19 Apr 2014 10:48:45 +0200 Subject: [PATCH 010/269] provide a comments overview listing, e.g. for moderation of comments --- expert/browser/report.pt | 1 + expert/field.py | 38 +++++++++++++- expert/report.py | 28 +++++++++- organize/comment/README.txt | 6 +++ organize/comment/configure.zcml | 22 ++++++++ organize/comment/loops_comment_de.dmp | 6 +++ organize/comment/report.py | 75 +++++++++++++++++++++++++++ organize/work/report.py | 37 +------------ 8 files changed, 175 insertions(+), 38 deletions(-) create mode 100644 organize/comment/loops_comment_de.dmp create mode 100644 organize/comment/report.py diff --git a/expert/browser/report.pt b/expert/browser/report.pt index ef0e32d..15caa68 100644 --- a/expert/browser/report.pt +++ b/expert/browser/report.pt @@ -16,6 +16,7 @@
diff --git a/expert/field.py b/expert/field.py index c98df2a..45aa1b1 100644 --- a/expert/field.py +++ b/expert/field.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# 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 @@ -29,6 +29,7 @@ from cybertools.composer.report.field import Field as BaseField from cybertools.composer.report.result import ResultSet from cybertools.stateful.interfaces import IStateful, IStatesDefinition from cybertools.util.date import timeStamp2Date +from cybertools.util.format import formatDate from loops.common import baseObject from loops.expert.report import ReportInstance from loops import util @@ -209,6 +210,41 @@ class TargetField(RelationField): return util.getObjectForUid(value) +# track fields + +class TrackDateField(Field): + + fieldType = 'date' + part = 'date' + format = 'short' + cssClass = 'right' + + def getValue(self, row): + value = self.getRawValue(row) + if not value: + 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'' + + def getSelectValue(self, row): + value = self.getRawValue(row) + if not value: + return '' + return timeStamp2ISO(value)[:10] + + +class TrackTimeField(TrackDateField): + + part = 'time' + + # sub-report stuff class SubReport(ReportInstance): diff --git a/expert/report.py b/expert/report.py index 44bde1f..bbab6ac 100644 --- a/expert/report.py +++ b/expert/report.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de +# 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 @@ -25,9 +25,11 @@ from zope.component import adapts from zope.interface import Interface, Attribute, implements from zope.cachedescriptors.property import Lazy from zope.security.proxy import removeSecurityProxy +from zope.traversing.api import getName from cybertools.composer.report.base import Report as BaseReport -from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria +from cybertools.composer.report.base import LeafQueryCriteria +from cybertools.composer.report.base import 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 @@ -53,6 +55,8 @@ class IReport(ILoopsAdapter, IReportParams): source='loops.expert.reportTypeSource', required=True) + name = Attribute('The name of the report.') + class IReportInstance(IBaseReport): """ The report-type-specific object (an adapter on the report) that @@ -68,6 +72,10 @@ class Report(AdapterBase): _contextAttributes = list(IReport) + @Lazy + def name(self): + return getName(self.context) + TypeInterfaceSourceList.typeInterfaces += (IReport,) @@ -79,9 +87,11 @@ class ReportInstance(BaseReport): rowFactory = Row view = None # set upon creation + #headerRowFactory = Row def __init__(self, context): self.context = context + self.name = self.type def getResultsRenderer(self, name, macros): return macros[name] @@ -105,6 +115,8 @@ class ReportInstance(BaseReport): if k in crit.parts.keys(): crit.parts[k].comparisonValue = v parts = Jeep(crit.parts) + if getattr(self, 'limitsCount', ''): + limits = None result = list(self.selectObjects(parts)) # may modify parts qc = CompoundQueryCriteria(parts) return ResultSet(self, result, rowFactory=self.rowFactory, @@ -161,3 +173,15 @@ class DefaultConceptReportInstance(ReportInstance): label = u'Default Concept Report' + +# specialized rows + +class TrackRow(Row): + + @staticmethod + def getContextAttr(obj, attr): + if attr in obj.context.metadata_attributes: + return getattr(obj.context, attr) + return obj.context.data.get(attr) + + diff --git a/organize/comment/README.txt b/organize/comment/README.txt index 3fe1b52..060571e 100644 --- a/organize/comment/README.txt +++ b/organize/comment/README.txt @@ -78,6 +78,12 @@ Viewing comments ('My comment', u'... ...', u'john') +Reporting +========= + + >>> from loops.organize.comment.report import CommentsOverview + + Fin de partie ============= diff --git a/organize/comment/configure.zcml b/organize/comment/configure.zcml index 2b78430..1b85ef1 100644 --- a/organize/comment/configure.zcml +++ b/organize/comment/configure.zcml @@ -37,4 +37,26 @@ factory="loops.organize.comment.browser.CreateComment" permission="zope.View" /> + + + + + + + + + + diff --git a/organize/comment/loops_comment_de.dmp b/organize/comment/loops_comment_de.dmp new file mode 100644 index 0000000..85fc571 --- /dev/null +++ b/organize/comment/loops_comment_de.dmp @@ -0,0 +1,6 @@ +type(u'report', u'Report', options=u'', + typeInterface='loops.expert.report.IReport', viewName=u'') +concept(u'comments_overview', u'\xdcbersicht Kommentare', u'report', + reportType=u'comments_overview') +concept(u'comments', u'Kommentare', u'query', options=u'', + viewName=u'list_comments.html') diff --git a/organize/comment/report.py b/organize/comment/report.py new file mode 100644 index 0000000..1641877 --- /dev/null +++ b/organize/comment/report.py @@ -0,0 +1,75 @@ +# +# 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 +# + +""" +Report views and definitions for comments listings and similar stuff. +""" + +from cybertools.util.jeep import Jeep +from loops.expert.browser.report import ReportConceptView +from loops.expert.field import Field, StateField, TargetField +from loops.expert.field import TrackDateField +from loops.expert.report import ReportInstance, TrackRow + + +class CommentsOverview(ReportConceptView): + + reportName = 'comments_overview' + + +timeStamp = TrackDateField('timeStamp', u'Timestamp', + description=u'The date and time the comment was posted.', + part='dateTime', + executionSteps=['sort', 'output']) +target = TargetField('taskId', u'Target', + description=u'The resource or concept the comment was posted at.', + executionSteps=['output']) +name = Field('name', u'Name', + description=u'The name addres of the poster.', + executionSteps=['output']) +email = Field('email', u'E-Mail Address', + description=u'The email addres of the poster.', + executionSteps=['output']) +subject = Field('subject', u'Subject', + description=u'The subject of the comment.', + executionSteps=['output']) +state = StateField('state', u'State', + description=u'The state of the comment.', + cssClass='center', + statesDefinition='organize.commentStates', + executionSteps=['query', 'sort', 'output']) + + +class CommentsRow(TrackRow): + + pass + + +class CommentsReportInstance(ReportInstance): + + type = "comments_overview" + label = u'Comments Overview' + + rowFactory = CommentsRow + + fields = Jeep((timeStamp, target, name, email, subject, state)) + defaultOutputFields = fields + defaultSortCriteria = (state, timeStamp) + + def selectObjects(self, parts): + return self.recordManager['comments'].values() diff --git a/organize/work/report.py b/organize/work/report.py index 238151d..8ccc515 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# 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 @@ -30,13 +30,13 @@ from cybertools.composer.report.field import CalculatedField from cybertools.composer.report.result import ResultSet, Row as BaseRow from cybertools.organize.interfaces import IWorkItems from cybertools.util.date import timeStamp2Date, timeStamp2ISO -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, StateField, \ TextField, HtmlTextField, UrlField from loops.expert.field import SubReport, SubReportField +from loops.expert.field import TrackDateField, TrackTimeField from loops.expert.report import ReportInstance from loops import util @@ -50,39 +50,6 @@ class WorkStatementView(ReportConceptView): # fields -class TrackDateField(Field): - - fieldType = 'date' - part = 'date' - format = 'short' - cssClass = 'right' - - def getValue(self, row): - value = self.getRawValue(row) - if not value: - 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'' - - def getSelectValue(self, row): - value = self.getRawValue(row) - if not value: - return '' - return timeStamp2ISO(value)[:10] - - -class TrackTimeField(TrackDateField): - - part = 'time' - - class DurationField(Field): cssClass = 'right' From 2a532dba86d99084a4b8e222ac0767aa5149fcac Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 19 Apr 2014 11:46:19 +0200 Subject: [PATCH 011/269] descending sort order on time stamp --- expert/field.py | 7 +++++++ organize/comment/report.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/expert/field.py b/expert/field.py index 45aa1b1..f744776 100644 --- a/expert/field.py +++ b/expert/field.py @@ -217,6 +217,7 @@ class TrackDateField(Field): fieldType = 'date' part = 'date' format = 'short' + descending = False cssClass = 'right' def getValue(self, row): @@ -239,6 +240,12 @@ class TrackDateField(Field): return '' return timeStamp2ISO(value)[:10] + def getSortValue(self, row): + value = self.getRawValue(row) + if value and self.descending: + return -value + return value or None + class TrackTimeField(TrackDateField): diff --git a/organize/comment/report.py b/organize/comment/report.py index 1641877..99c451e 100644 --- a/organize/comment/report.py +++ b/organize/comment/report.py @@ -34,7 +34,7 @@ class CommentsOverview(ReportConceptView): timeStamp = TrackDateField('timeStamp', u'Timestamp', description=u'The date and time the comment was posted.', - part='dateTime', + part='dateTime', descending=True, executionSteps=['sort', 'output']) target = TargetField('taskId', u'Target', description=u'The resource or concept the comment was posted at.', From fab93d8ceb6d7fd3af7a9bf28cd9fcda753d92b3 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 25 Apr 2014 13:28:19 +0200 Subject: [PATCH 012/269] work in progress: qualification overview --- expert/README.txt | 6 ++-- expert/search.txt | 14 ++++----- external/README.txt | 6 ++-- knowledge/data/knowledge_de.dmp | 6 ++++ knowledge/data/knowledge_update_de.dmp | 4 +++ knowledge/qualification/browser.py | 9 +++--- knowledge/qualification/configure.zcml | 22 +++++++++++++ knowledge/qualification/report.py | 43 ++++++++++++++++++++++++++ knowledge/tests.py | 2 ++ organize/README.txt | 2 +- system/sync/README.txt | 2 +- xmlrpc/README.txt | 12 +++---- 12 files changed, 102 insertions(+), 26 deletions(-) create mode 100644 knowledge/qualification/report.py diff --git a/expert/README.txt b/expert/README.txt index d3fd786..545c865 100644 --- a/expert/README.txt +++ b/expert/README.txt @@ -27,7 +27,7 @@ configuration): >>> concepts, resources, views = t.setup() >>> len(concepts) + len(resources) - 36 + 38 >>> loopsRoot = site['loops'] @@ -47,11 +47,11 @@ Type- and text-based queries >>> from loops.expert import query >>> qu = query.Title('ty*') >>> list(qu.apply()) - [0, 2, 65] + [0, 2, 69] >>> qu = query.Type('loops:*') >>> len(list(qu.apply())) - 36 + 38 >>> qu = query.Type('loops:concept:predicate') >>> len(list(qu.apply())) diff --git a/expert/search.txt b/expert/search.txt index 18df101..d66ed00 100755 --- a/expert/search.txt +++ b/expert/search.txt @@ -66,13 +66,13 @@ zcml in real life: >>> t = searchView.typesForSearch() >>> len(t) - 15 + 16 >>> t.getTermByToken('loops:resource:*').title 'Any Resource' >>> t = searchView.conceptTypesForSearch() >>> len(t) - 12 + 13 >>> t.getTermByToken('loops:concept:*').title 'Any Concept' @@ -91,7 +91,7 @@ a controller attribute for the search view. >>> searchView.submitReplacing('1.results', '1.search.form', pageView) 'submitReplacing("1.results", "1.search.form", - "http://127.0.0.1/loops/views/page/.target96/@@searchresults.html");...' + "http://127.0.0.1/loops/views/page/.target100/@@searchresults.html");...' Basic (text/title) search ------------------------- @@ -177,7 +177,7 @@ of the concepts' titles: >>> request = TestRequest(form=form) >>> view = Search(page, request) >>> view.listConcepts() - u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '101'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '103'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '105'}]}" + u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '105'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '107'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '109'}]}" Preset Concept Types on Search Forms ------------------------------------ @@ -219,9 +219,9 @@ and thus include the customer type in the preset search types. >>> searchView.conceptsForType('loops:concept:customer') [{'token': 'none', 'title': u'not selected'}, - {'token': '74', 'title': u'Customer 1'}, - {'token': '76', 'title': u'Customer 2'}, - {'token': '78', 'title': u'Customer 3'}] + {'token': '78', 'title': u'Customer 1'}, + {'token': '80', 'title': u'Customer 2'}, + {'token': '82', 'title': u'Customer 3'}] Let's use this new search option for querying: diff --git a/external/README.txt b/external/README.txt index 24e3816..5fddc0e 100644 --- a/external/README.txt +++ b/external/README.txt @@ -17,7 +17,7 @@ Let's set up a loops site with basic and example concepts and resources. >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> len(concepts), len(resources), len(views) - (33, 3, 1) + (35, 3, 1) Importing loops Objects @@ -44,7 +44,7 @@ Creating the corresponding objects >>> loader = Loader(loopsRoot) >>> loader.load(elements) >>> len(concepts), len(resources), len(views) - (34, 3, 1) + (36, 3, 1) >>> from loops.common import adapted >>> adMyquery = adapted(concepts['myquery']) @@ -118,7 +118,7 @@ Extracting elements >>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export')) >>> elements = list(extractor.extract()) >>> len(elements) - 65 + 67 Writing object information to the external storage -------------------------------------------------- diff --git a/knowledge/data/knowledge_de.dmp b/knowledge/data/knowledge_de.dmp index d1a0e36..83367d9 100644 --- a/knowledge/data/knowledge_de.dmp +++ b/knowledge/data/knowledge_de.dmp @@ -4,6 +4,8 @@ type(u'competence', u'Kompetenz', viewName=u'', type(u'person', u'Person', viewName=u'', typeInterface=u'loops.knowledge.interfaces.IPerson', options=u'action.portlet:createQualification,editPerson') +type(u'report', u'Report', viewName=u'', + typeInterface='loops.expert.report.IReport') type(u'task', u'Aufgabe', viewName=u'', typeInterface=u'loops.knowledge.interfaces.ITask', options=u'action.portlet:createTask,editTask') @@ -26,6 +28,10 @@ concept(u'requires', u'requires', u'predicate') concept(u'issubtype', u'is Subtype', u'predicate', options=u'hide_children', predicateInterface='loops.interfaces.IIsSubtype') +# reports +concept(u'qualification_overview', u'Qualification Overview', u'report', + reportType=u'qualification_overview') + # structure child(u'general', u'competence', u'standard') child(u'general', u'depends', u'standard') diff --git a/knowledge/data/knowledge_update_de.dmp b/knowledge/data/knowledge_update_de.dmp index 403589f..2a359c2 100644 --- a/knowledge/data/knowledge_update_de.dmp +++ b/knowledge/data/knowledge_update_de.dmp @@ -26,6 +26,10 @@ concept(u'requires', u'requires', u'predicate') concept(u'issubtype', u'is Subtype', u'predicate', options=u'hide_children', predicateInterface='loops.interfaces.IIsSubtype') +# reports +concept(u'qualification_overview', u'Qualification Overview', u'report', + reportType=u'qualification_overview') + # structure child(u'general', u'competence', u'standard') child(u'general', u'depends', u'standard') diff --git a/knowledge/qualification/browser.py b/knowledge/qualification/browser.py index 29b53d6..e9e6641 100644 --- a/knowledge/qualification/browser.py +++ b/knowledge/qualification/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# 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 @@ -18,7 +18,7 @@ """ Definition of view classes and other browser related stuff for the -loops.knowledge package. +loops.knowledge.qualification package. """ from zope import interface, component @@ -27,10 +27,9 @@ from zope.cachedescriptors.property import Lazy from loops.expert.browser.report import ResultsConceptView from loops.knowledge.browser import template, knowledge_macros -from loops.knowledge.qualification.base import QualificationRecord -class PersonQualificationView(ResultsConceptView): +class Qualifications(ResultsConceptView): - pass + reportName = 'qualification_overview' diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index dea246f..a6ca61b 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -15,4 +15,26 @@ + + + + + + + + + + diff --git a/knowledge/qualification/report.py b/knowledge/qualification/report.py new file mode 100644 index 0000000..7b0a8e8 --- /dev/null +++ b/knowledge/qualification/report.py @@ -0,0 +1,43 @@ +# +# 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 +# + +""" +Qualification management report definitions. +""" + +from cybertools.util.jeep import Jeep +from loops.expert.report import ReportInstance +from loops.organize.work.report import WorkRow +from loops.organize.work.report import deadline, day, task, party, state +from loops.organize.work.report import workTitle, workDescription + + +class QualificationOverview(ReportInstance): + + type = "qualification_overview" + label = u'Qualification Overview' + + rowFactory = WorkRow + + fields = Jeep((day, deadline, party, task, workTitle, state)) + + taskTypeNames = ('folder','query', 'competence',) + defaultOutputFields = fields + + def getTasks(self, parts): + return [] diff --git a/knowledge/tests.py b/knowledge/tests.py index 8340a79..11f8ec4 100755 --- a/knowledge/tests.py +++ b/knowledge/tests.py @@ -7,6 +7,7 @@ from zope import component from zope.interface.verify import verifyClass from zope.testing.doctestunit import DocFileSuite +from loops.expert.report import IReport, Report from loops.knowledge.qualification.base import Competence from loops.knowledge.survey.base import Questionnaire, Question, FeedbackItem from loops.knowledge.survey.interfaces import IQuestionnaire, IQuestion, \ @@ -19,6 +20,7 @@ importPath = os.path.join(os.path.dirname(__file__), 'data') def importData(loopsRoot): + component.provideAdapter(Report, provides=IReport) baseImportData(loopsRoot, importPath, 'knowledge_de.dmp') def importSurvey(loopsRoot): diff --git a/organize/README.txt b/organize/README.txt index 110efe6..c80f403 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -410,7 +410,7 @@ Send Email to Members >>> form.subject u"loops Notification from '$site'" >>> form.mailBody - u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.113\n\n' + u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.117\n\n' Show Presence of Other Users diff --git a/system/sync/README.txt b/system/sync/README.txt index 5ed477f..143c6a9 100644 --- a/system/sync/README.txt +++ b/system/sync/README.txt @@ -18,7 +18,7 @@ Let's set up a loops site with basic and example concepts and resources. >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> len(concepts), len(resources), len(views) - (33, 3, 1) + (35, 3, 1) >>> from cybertools.tracking.btree import TrackingStorage >>> from loops.system.job import JobRecord diff --git a/xmlrpc/README.txt b/xmlrpc/README.txt index 3ea6557..de55ea2 100755 --- a/xmlrpc/README.txt +++ b/xmlrpc/README.txt @@ -35,7 +35,7 @@ ZCML setup): Let's look what setup has provided us with: >>> len(concepts) - 22 + 24 Now let's add a few more concepts: @@ -73,7 +73,7 @@ applied in an explicit assignment. >>> sorted(t['name'] for t in xrf.getConceptTypes()) [u'competence', u'customer', u'domain', u'file', u'note', u'person', - u'predicate', u'task', u'textdocument', u'topic', u'type'] + u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> sorted(t['name'] for t in xrf.getPredicates()) [u'depends', u'issubtype', u'knows', u'ownedby', u'provides', u'requires', u'standard'] @@ -96,7 +96,7 @@ All methods that retrieve one object also returns its children and parents: u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) [u'competence', u'customer', u'domain', u'file', u'note', u'person', - u'predicate', u'task', u'textdocument', u'topic', u'type'] + u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> pa = defaultPred['parents'] >>> len(pa) @@ -115,7 +115,7 @@ We can also retrieve children and parents explicitely: u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) [u'competence', u'customer', u'domain', u'file', u'note', u'person', - u'predicate', u'task', u'textdocument', u'topic', u'type'] + u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> pa = xrf.getParents('5') >>> len(pa) @@ -174,14 +174,14 @@ Updating the concept map >>> topicId = xrf.getObjectByName('topic')['id'] >>> xrf.createConcept(topicId, u'zope2', u'Zope 2') - {'description': u'', 'title': u'Zope 2', 'type': '36', 'id': '72', + {'description': u'', 'title': u'Zope 2', 'type': '38', 'id': '76', 'name': u'zope2'} The name of the concept is checked by a name chooser; if the corresponding parameter is empty, the name will be generated from the title. >>> xrf.createConcept(topicId, u'', u'Python') - {'description': u'', 'title': u'Python', 'type': '36', 'id': '74', + {'description': u'', 'title': u'Python', 'type': '38', 'id': '78', 'name': u'python'} If we try to deassign a ``hasType`` relation nothing will happen; a From 8ab637c402763d2fe792c68b3708b5c0c3ae4881 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 26 Apr 2014 15:52:28 +0200 Subject: [PATCH 013/269] allow for additional access control (without acquisition/inheritance) on queries and types via 'access_permission' option --- security/common.py | 27 +++++++++++++++++++++++++-- type.py | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/security/common.py b/security/common.py index f1904ec..f87317a 100644 --- a/security/common.py +++ b/security/common.py @@ -36,6 +36,7 @@ from zope.security.management import getInteraction from zope.traversing.api import getName from zope.traversing.interfaces import IPhysicallyLocatable +from cybertools.meta.interfaces import IOptions from loops.common import adapted from loops.interfaces import ILoopsObject, IConcept from loops.interfaces import IAssignmentEvent, IDeassignmentEvent @@ -66,13 +67,35 @@ workspaceGroupsFolderName = 'gloops_ws' # checking and querying functions +def getOption(obj, option, checkType=True): + opts = component.queryAdapter(adapted(obj), IOptions) + if opts is not None: + opt = opts(option, None) + if opt: + return opt[0] + if not checkType: + return None + typeMethod = getattr(obj, 'getType', None) + if typeMethod is not None: + opts = component.queryAdapter(adapted(typeMethod()), IOptions) + if opts is not None: + opt = opts(option, [None]) + if opt: + return opt[0] + return None + def canAccessObject(obj): - return canAccess(obj, 'title') + if not canAccess(obj, 'title'): + return False + perm = getOption(obj, 'access_permission') + if not perm: + return True + return checkPermission(perm, obj) def canListObject(obj, noCheck=False): if noCheck: return True - return canAccess(obj, 'title') + return canAccessObject(obj) def canAccessRestricted(obj): return checkPermission('loops.ViewRestricted', obj) diff --git a/type.py b/type.py index 0f9ffee..298e112 100644 --- a/type.py +++ b/type.py @@ -110,7 +110,7 @@ class LoopsType(BaseType): @Lazy def typeProvider(self): # TODO: unify this type attribute naming... - return self.context.resourceType + return getattr(self.context, 'resourceType', None) @Lazy def options(self): From 21ac74f764ee91fb6c260af605e4b6490477b4d2 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 27 Apr 2014 22:38:51 +0200 Subject: [PATCH 014/269] add 'delete' action to comments --- browser/configure.zcml | 8 ++++++++ browser/form.py | 26 +++++++++++++++++++++++--- organize/comment/browser.py | 17 +++++++++++++++-- organize/stateful/browser.py | 1 + 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/browser/configure.zcml b/browser/configure.zcml index 75e5d7d..e7209b8 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -561,6 +561,14 @@ factory="loops.browser.concept.TabbedPage" permission="zope.View" /> + + + + Date: Tue, 29 Apr 2014 10:36:22 +0200 Subject: [PATCH 015/269] provide report and view for all qualifications --- expert/browser/results.pt | 4 +++- knowledge/qualification/report.py | 26 ++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/expert/browser/results.pt b/expert/browser/results.pt index 4a04496..e64f96f 100644 --- a/expert/browser/results.pt +++ b/expert/browser/results.pt @@ -26,6 +26,7 @@
+
@@ -34,7 +35,8 @@ tal:attributes="class col/cssClass" i18n:translate="" /> - + - + - - - + + +
Date: Tue, 29 Apr 2014 17:06:19 +0200 Subject: [PATCH 016/269] add boolean fields for controlling feedback/evaluation variations --- knowledge/survey/browser.py | 5 +++++ knowledge/survey/interfaces.py | 14 ++++++++++++++ knowledge/survey/view_macros.pt | 16 ++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index d9075d6..c8f9bf6 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -55,6 +55,11 @@ class SurveyView(ConceptView): if self.editable: return 'index.html' + @Lazy + def showFeedbackText(self): + sft = self.adapted.showFeedbackText + return sft is None and True or sft + def results(self): result = [] response = None diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index e2adffc..e66656e 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -38,6 +38,20 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): default=4, required=True) + showFeedbackText = schema.Bool( + title=_(u'Show Feedback Text'), + description=_(u'If not set no feedback text will be shown on ' + u'the results page.'), + default=True, + required=False) + + showTeamResults = schema.Bool( + title=_(u'Show Team Results'), + description=_(u'If set the team-related columns will be shown on ' + u'the results page.'), + default=False, + required=False) + feedbackHeader = schema.Text( title=_(u'Feedback Header'), description=_(u'Text that will appear at the top of the feedback page.'), diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 9e7593c..fd43e63 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -17,13 +17,25 @@ - + + + + + +
CategoryResponseResponse %RankTeam Score %Team Rank
- + + + + + +
From 66bf217ee8c8672b07f78a60d1860d709ff8d426 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 2 May 2014 14:45:58 +0200 Subject: [PATCH 017/269] provide team evaluation of survey --- knowledge/survey/browser.py | 41 +++++++++++++++++++++++++++++---- knowledge/survey/response.py | 8 ++++--- knowledge/survey/view_macros.pt | 24 ++++++++++++------- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index c8f9bf6..7a605bc 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -60,9 +60,30 @@ class SurveyView(ConceptView): sft = self.adapted.showFeedbackText return sft is None and True or sft + def getTeamData(self, respManager, myResponse): + result = [myResponse] + pred = self.conceptManager.get('ismember') + if pred is None: + return result + personId = respManager.personId + person = self.getObjectForUid(personId) + inst = person.getParents([pred]) + if inst: + for c in inst[0].getChildren([pred]): + uid = self.getUidForObject(c) + if uid != personId: + data = respManager.load(uid) + if data: + resp = Response(self.adapted, None) + for qu in self.adapted.questions: + resp.values[qu] = data[qu.uid] + result.append(resp) + return result + def results(self): - result = [] + values = [] response = None + respManager = Responses(self.context) form = self.request.form if 'submit' in form: self.data = {} @@ -75,15 +96,25 @@ class SurveyView(ConceptView): value = int(value) self.data[uid] = value response.values[question] = value - Responses(self.context).save(self.data) + respManager.save(self.data) self.errors = self.check(response) if self.errors: return [] + ranks = averages = [] if response is not None: - result = response.getGroupedResult() - return [dict(category=r[0].title, text=r[1].text, + if self.adapted.showTeamResults: + values, ranks, averages = response.getTeamResult( + self.getTeamData(respManager, response)) + else: + values = response.getGroupedResult() + result = [dict(category=r[0].title, text=r[1].text, score=int(round(r[2] * 100))) - for r in result] + for r in values] + if ranks or averages: + for idx, qgdata in enumerate(result): + qgdata['rank'] = ranks[idx] + qgdata['average'] = int(round(averages[idx] * 100)) + return result def check(self, response): errors = [] diff --git a/knowledge/survey/response.py b/knowledge/survey/response.py index 3841c4f..6f5635d 100644 --- a/knowledge/survey/response.py +++ b/knowledge/survey/response.py @@ -43,9 +43,11 @@ class Responses(BaseRecordManager): self.storage.saveUserTrack(self.uid, 0, self.personId, data, update=True, overwrite=True) - def load(self): - if self.personId: - tracks = self.storage.getUserTracks(self.uid, 0, self.personId) + def load(self, personId=None): + if personId is None: + personId = self.personId + if personId: + tracks = self.storage.getUserTracks(self.uid, 0, personId) if tracks: return tracks[0].data return {} diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index fd43e63..094a82f 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -19,22 +19,30 @@
Category Response%% RankTeam Score %Team RankRankTeam Score %Team Rank
- + - - - + + +
From a306e11cecb46836d30d9054d4b42f698439336c Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 3 May 2014 10:45:41 +0200 Subject: [PATCH 018/269] allow actions also in previous work items of a run --- organize/work/browser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/organize/work/browser.py b/organize/work/browser.py index 8975aba..a781c8b 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de +# 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 @@ -151,8 +151,8 @@ class WorkItemDetails(TrackDetails): target=self.object, addParams=dict(id=self.track.__name__)) actions = [info, WorkItemStateAction(self)] - if self.isLastInRun and self.allowedToEditWorkItem: - #if self.allowedToEditWorkItem: + #if self.isLastInRun and self.allowedToEditWorkItem: + if self.allowedToEditWorkItem: self.view.registerDojoDateWidget() self.view.registerDojoNumberWidget() self.view.registerDojoTextarea() From f9bce78d815996ebdb1ebd3f8c0658a558203091 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 7 May 2014 09:03:40 +0200 Subject: [PATCH 019/269] provide translations --- knowledge/survey/view_macros.pt | 2 +- locales/de/LC_MESSAGES/loops.mo | Bin 24685 -> 24869 bytes locales/de/LC_MESSAGES/loops.po | 14 +++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 094a82f..b7b6a4d 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -20,7 +20,7 @@
Response % Rank
+ i18n:translate="">Score + + i18n:translate="">Team Score - @@ -37,12 +37,13 @@ tal:content="fbitem/text" />
Category Response Score %Rank Rank Team Score %Team Rank
+ - - +
diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index bbb2a9a7f5107d8028fa81bf3d348adc75f9dcc5..6d61ea85d7bedcadd176ca8342e43bdaccc29169 100644 GIT binary patch delta 4843 zcmXZe36Rd`9l-JbyBq7;b*9mbww521X)1--`5oWVml77GYe`YfIJooc^e$VrN*TMG^mG32v z-mjgk6-7~bVH9QH^H?7@VFTPA>ie(_^&ev$d>Ki4a$0!`4!9aeB2JjZv!xN$XEHe8?f-FSJh0<)-ZLH>(&@jo{_hi-HYpTO(b8MBI` zs5kb-Nw@^f+;1@-FQXZ#M)yf1`^5odVpAHLU^;dV?cIXC&=VA+6USjDPDKM*fSz~} zdcxxDiSo67|fh<4+S%z*j2RdoDk=t9x(_-)ih ze;;zt$JrAdR}$K1qZwX-ZoD2HmwcImk7z%-;4jdD$3p!S8pyfOej|7by_za?!P-yq z`;OUY2FlS?{|0?qAE4`g80uG%t4l^7Q}8w4K^IE@W*kUEbb*#=e0$h&#_(glsM|KH) zEB{7Qd>1|GL#%~4qvI>eM-Nhf=~#?)a3FfH5u?e!pGO%DKEnA}8&{$WSD*`Q!93iB zZtyEKz~iC+0y_R9G*dS*4-;eJ1LdRbgV8IVf?nmKG34KljWl?7yMjMKQ*;7-TvtN< zKDMOZU~C*fXEb9&(L0`jex}RN_0|U~(SQ%4`<*~Dc`-@BRD2Q|s?j^n92ZB}9nHu{ zbm0m37|sgL!yeQZp(opeZhROs@SRXUjrFPj9bNBZtcS@v6pZ8nn%b=K@pqbyY1G@I z3+16#)ENh3F}l!dtb-fT0AC8eiuu&v#K-XrI_@^QUKMhXWb`O5L}?Sk4+;8Mvav3< zKqs~jc0~i|g9f|;9lsV$@p`QJ-qEW$glq5=dVqct<0}}A*?#|XDEQf|MDMl|UFa}+ zr>C$1UPK?w4YdCO+TZx8_;c%kZafsT@M$yy&!8uM0nOMpG~=&ey5IjH3YmBWop=lz z;%Rh)%UJV9&=Xf<34VpMaMYyu&HM;W^=WjYn`q$oup6dL4sQz0=txYu@k9zfhMB=d zVZaLHkWmG?;2+SGeuxe67J9;mScth#^FXl_yWjzQ0?%Uye1P?_?UXo>f+^(R)C{Ac z36`K6%|I8Jj|TPwoQr$V*I$KR+5gbbsa0v5p&sZu!_f9A=mD3anO%ck;fv_o+E+^c z-QXY%Ms^gt7LpNxH+1Me}i-SYa6PBPUoP>>WM(AILuCoe@u>#%jm+0@wX)MIE z$R8!Xe+vF_$@vza3{JxWJc7RFOK1xJi*8(Ndb~jcG*eB{)Q<}7W$5^sXkd%bOszx5 zCDA~3U>(2zofP~5c^&<0xF3Dp$I%m>LpQz@+W&z*zS}qk>(7WYR)$Tf&qg2HN_76F z(7z4c_!V@$SMe#o|NRu6#5-u}dd-X@Ek2qW2qZ!UduQV48pktDP3-v%>YacYik>~>BL%kFYY$m$Fvfu_Zu zCc5FGnK&cO(SUN%4SS)V+i>)im!o&T6}_6*&~-jQ z7runfyM_jG3(aUXdh%NHxJHL=rZPN{n%cTv8c!iLKj(||#KT=>dB3B!t4aGcY3&lJ zZSC(gNmUdk(o!3XX4OgSU6{H$q(ie*MrnQ`)wQgDTJzF{^S&F6SiN$=lJW&ho?E(V NRh})WP16q6{XZYXD0Tn< delta 4868 zcmXZe3v8Cv8Nl)P`$}n{B2X?Z7vU@AB88$LP@r7pB2bWPxhhZ=H6;aw^+@c9}|&Yzhc5C_l zy>x5P%sr3p>?O2c6*_Jg8tB_-0PkUI){p+ogFC!{4!Dd)ehnRX6Ng|Ow!uNA4veDd z=t9fTah2%Z*oZE)EtKCxFK;zA#lxX|8uM8{`YbfuL{pSGI1ZpO8bC92hk58iMd;By zfKE6zw9i5pT87TEG1PBH1FH({dqVjTrrpsg9?Za(=+3VOZ=eg_Mgz$p4eq!xx^ zx$@9)ozWvG3iZ9vex>NR;lZ)N$wSD$5znH+lr0GjmB9^Y`?F{uRak$P=o!9^PW;Ev zek_zv2R}svxPWG$7QI{7(GOQNl>Gb9bZBgcU@pwtg9^6R<-h=DV56bJ9hsV*BUO)rAi1xpVPSjvT zd^eh)--i@>Is2mh%0hhwn&CC*!fCW$`b8eRM7z-me}i^B9LguqK+c5v%fWBaqq&Vv znDby1&Br`61MARKe}LYtkI{M0hVn(^=+e>GJb0V0qZ4I~j00(bPS750FGAagp{Xwm zPQ~_=XQOvvEjn%!y6{Wr+p`1Ba5cK2li1exzcy6-fOc#__?~^&-~dcf9*?GM89Koh z^zGRh>VJy{dOEb%Vn@oi(E0L5$MYAV~hz=Nz?r<)4$4VT8yU8{vHkRNNE2%+W!)oscV?R%yIFCx}xvl@GaBFv!JlI%%I{(qJdO7I4xR5dvXOL@m>BPV&;Ux%fY+e?H=rp_WBvP%9!)i_#S`cT20t7h!31pK`~MgZzBX&ov#mlW zI)I+(3A`K6p_k?|+I|OZZ#5}?-MXO*kHjW81I@q_=#HO5Gqw%Q_#2qv`(Mq&UHBe4 z@Gv&TQ|JQcvHp&rJFdfrFgZDj9>MYGo!N_~`V_j*H8k+=u?IGs67Ce5(Q%k|;i)`$ z8RiF$AjQ+j zzo{8TMJ|@13(Z3(ScC@l3eLx!Xu!A8Bg>i|pJf+pM!6K7XF{l-kM>`KW_AmDgs-A^ zYwvXO?*i{rVPqd*FFc6`@_p#|KlHtAR37*1hz8yxl>4Bm9*U-RBDTUA=={sk`PZQV zK8yTO6}^$>!4J)+=%xAwdt>H|_)JUC$Y*08T#N>`6AfT@Xx|sghk{4Y%X|X;fPH}m z@B{& z{)v6@bL2-U9p%o7e_VRwR2r6Jcl;2&%~#MAHk}{^0IYu<(a6g}eL3b)u0S)h9^K(qbm1LnV0%J+ z4Z5MD*cux=%0~*@p_y8Q2DI`~@^4DkQ{jRe(f9bL$X`v-j?nQl>_z!2Y>%zy#S1=w zPBa{yxGXpgTTz~iX6Pw2kRPKP+>VZWa~}D(!&{+ZKN`RxbVnyc{aJL!wP>JM(K~Ys zO=(>)IX})&HX2ZVFokCLUi3)&q5%v{^Wa3I(Azo|jc^V+K}9GpK?7Tb9>M0|b~LbG zp@AJlkLnNTf`1G3pJN-!UxjjFK|FsthX*HUflg3>W~2uiP+xSxG3e_y1HI+Xqi4Sh zJ(~UKJQvWwub|_;Ljy@v#2IaZ?z}A~?7D?QT~srpZR2FJL!#!f_SZ8K(QD-#^FoxH!K=G@T2R@tc~D^k-JR;;MGJ*`v1*7oHC kllMHau(H0kG8(mJ_2W-gKK|s&B~L$HUqzQ?Gv8_Se{H!bk^lez diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 00fd886..a674499 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.0\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2014-05-07 12:00 CET\n" +"PO-Revision-Date: 2014-05-24 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -265,17 +265,17 @@ msgstr "Bitte beantworten Sie die angegebene Mindestanzahl an Fragen je Fragengr msgid "Obligatory question, must be answered" msgstr "Pflichtfrage, muss beantwortet werden" -msgid "Score %" -msgstr "Bewertung %" +msgid "Score" +msgstr "Bewertung" -msgid "Team Score %" -msgstr "Durchschnitt Team %" +msgid "Team Score" +msgstr "Durchschnitt Team" msgid "Rank" -msgstr "Rang im Team" +msgstr "Rang" msgid "Team Rank" -msgstr "Rang des Teams" +msgstr "Rang Team" # competence (qualification) From cb2ac10524ef705f90cef88c28095d610b474ea6 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 25 May 2014 10:37:43 +0200 Subject: [PATCH 025/269] specify columns of feedback table on questionnaire edit form --- knowledge/survey/browser.py | 22 ++++++++++++++++++--- knowledge/survey/interfaces.py | 20 +++++++++---------- knowledge/survey/view_macros.pt | 34 ++++++++++---------------------- locales/de/LC_MESSAGES/loops.mo | Bin 24848 -> 24851 bytes locales/de/LC_MESSAGES/loops.po | 6 +++--- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 5de6302..10f90cf 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -60,6 +60,22 @@ class SurveyView(ConceptView): sft = self.adapted.showFeedbackText return sft is None and True or sft + @Lazy + def feedbackColumns(self): + cols = self.adapted.feedbackColumns + if not cols: + cols = [ + dict(name='text', label=u'Response'), + dict(name='score', label=u'Score')] + return cols + + @Lazy + def showTeamResults(self): + for c in self.feedbackColumns: + if c['name'] in ('average', 'teamRank'): + return True + return False + def getTeamData(self, respManager): #result = [myResponse] result = [] @@ -117,10 +133,10 @@ class SurveyView(ConceptView): result = [dict(category=r['group'].title, text=r['feedback'].text, score=int(round(r['score'] * 100)), rank=r['rank']) for r in values] - if self.adapted.showTeamResults: + if self.showTeamResults: teamData = self.getTeamData(respManager) - values = response.getTeamResult(values, teamData) - for idx, r in enumerate(values): + teamValues = response.getTeamResult(values, teamData) + for idx, r in enumerate(teamValues): result[idx]['average'] = int(round(r['average'] * 100)) result[idx]['teamRank'] = r['rank'] return result diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index e66656e..9799865 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -23,6 +23,7 @@ Interfaces for surveys used in knowledge management. from zope.interface import Interface, Attribute from zope import interface, component, schema +from cybertools.composer.schema.grid.interfaces import Records from cybertools.knowledge.survey import interfaces from loops.interfaces import IConceptSchema, ILoopsAdapter from loops.util import _ @@ -38,19 +39,16 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): default=4, required=True) - showFeedbackText = schema.Bool( - title=_(u'Show Feedback Text'), - description=_(u'If not set no feedback text will be shown on ' - u'the results page.'), - default=True, + feedbackColumns = Records( + title=_(u'Feedback Columns'), + description=_(u'Column definitions for the results table ' + u'on the feedback page.'), + default=[], required=False) - showTeamResults = schema.Bool( - title=_(u'Show Team Results'), - description=_(u'If set the team-related columns will be shown on ' - u'the results page.'), - default=False, - required=False) + feedbackColumns.column_types = [ + schema.Text(__name__='name', title=u'Column Name',), + schema.Text(__name__='label', title=u'Column Label'),] feedbackHeader = schema.Text( title=_(u'Feedback Header'), diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 56af418..bb976ba 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -18,33 +18,19 @@ - - - - - - - +
CategoryResponseScoreRankTeam ScoreTeam Rank
- - - - - - - + + +
diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 6d61ea85d7bedcadd176ca8342e43bdaccc29169..26ec710435d2057691fa31ac636944538849dd62 100644 GIT binary patch delta 1397 zcmXZbO-R&17{~EhGgH$w+gxATEX6JAASh-j5>`h3Lt2V-eQS3K$}~vmMGp~wD2yVO zl9WR7um=f(s91_F5g0@z2_0lFL7qB9qyLT$-=l&O zH{Tc&=rkq=D{(c3(T{bQi>+9K4r+cJHE#&l;t00jH7v(hSc;2Shrh52w{;m)h#gpu zy_#RcjS5yIia3vN-jmg3QY8*uEAHo3E*zrzl zYt;2l*iImi35V>&K09F;wb3ZBDaPf&?oV*}1%C7Nzynqeaz#$Mct4={`wR0sc| zI-BeEcnOM8ttv&e-m&p+)Pfim;~=V27f|yqp$ZvCZ8(7|a2nTR3XAYL>VWT1^D;K> zF7mN~z%n}M@AX>Njq4aEP9Hl9MA=phF2J08Hiqh6OYEiGY$+{a$7(1vA#ZiTvM4iw@t-E4>AGhNvQ~@)n13k6zm#72J zqpM0k@S#WZ3Dwfi)@9V=`H3nhC+_J-wK|Bp(`r-!TTuJdqaJG`s=#*C`iLDzQH90g z^j`^v2}l=J*$q@-)2O?;hf4Uu#^_QN)e_9#cV^)DvD1TR RH-*!o&PY}ko{G-o{Rgd0sgnQz delta 1393 zcmXZbPe@cz6vy#9PFj-ouCkaHzMv4$7j6lL9 z%Mc7)L@kOI(ky~#5rQST2@xVFTC|A3DJbY47=3@eyZXF)-hKC+d+vR=7F;(LT=QOE z(qoLt4;hn%Wtfdsn1g$86E>m`kKktPLCrghc{qagIF7}*h=uq81NaknV0GA-E!c*8 zF&Z{Wvyq1h0xDqIPPk*o53KW8#QP^$imy=xq)`R@#!U1^jLE<})YykwzXbEK(vE}H z#z@lHVG99u;uvnjPCH=`wNL_scnOv8F)Gm_*5VSD<2vrg(o@EqzzA03L)?k0s1E)? zbv82@brR&DT2+8*eY1^+P#?rlg`Gil>LO~MzE>fWs0F9cjk8#SbC`=yQ5$@Pnzw4> z$@e^z5lCYzW_LO*3t<7{Zq%6#qZYVq-(N!|zKL4!Hg@0~HsKdk=K?XO&>(JL+>F{# z8+xhVgn1|;a2mDHC~9X{u@0wE1uon7pHT~ZLnT;8ovpjexf6xfN>rz6P=(jo_yH_r z+>W{AH~logIyI=vx(`+0Vbu5Sc6<_5 zSge=+E5RTEIf|-m0@cD<)LGp_C46S%OIXZ!#g5ld34Wly|7+vkKBr@TR6!M}JOR|- zcCfF-x#i~x=\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -266,10 +266,10 @@ msgid "Obligatory question, must be answered" msgstr "Pflichtfrage, muss beantwortet werden" msgid "Score" -msgstr "Bewertung" +msgstr "Ergebnis %" msgid "Team Score" -msgstr "Durchschnitt Team" +msgstr "Durchschnitt Team %" msgid "Rank" msgstr "Rang" From 61484aea7056d6945b2228aab7bbe49a7900648d Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 25 May 2014 12:26:55 +0200 Subject: [PATCH 026/269] top vertical alignment for table cells --- knowledge/survey/view_macros.pt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index bb976ba..b5d088e 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -24,10 +24,12 @@ col['name'] != 'text' and 'center' or None" tal:content="col/label" /> - - + + - From 49b7ef038214d108e87a479d597c4fad804884fa Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 25 May 2014 12:29:27 +0200 Subject: [PATCH 027/269] top vertical alignment for table cells --- knowledge/survey/view_macros.pt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index b5d088e..0f5d1c9 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -24,7 +24,8 @@ col['name'] != 'text' and 'center' or None" tal:content="col/label" /> - + From 5f81ecfed2b5a8bb375c1e5a30269580d7ee56ee Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 26 May 2014 10:17:39 +0200 Subject: [PATCH 028/269] handle deleted tasks gracefully --- organize/party.py | 4 +++- organize/tracking/report.py | 5 ++++- organize/work/browser.py | 3 +++ organize/work/work_macros.pt | 5 +++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/organize/party.py b/organize/party.py index 1ad33d5..c83ac40 100644 --- a/organize/party.py +++ b/organize/party.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# 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 @@ -56,6 +56,8 @@ PredicateInterfaceSourceList.predicateInterfaces += (IHasRole,) def getPersonForUser(context, request=None, principal=None): + if context is None: + return None if principal is None: principal = getattr(request, 'principal', None) if principal is None: diff --git a/organize/tracking/report.py b/organize/tracking/report.py index db04490..d1ee35e 100644 --- a/organize/tracking/report.py +++ b/organize/tracking/report.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# 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 @@ -264,6 +264,9 @@ class TrackDetails(BaseView): @Lazy def objectData(self): obj = self.object + if obj is None: + return dict(object=None, title='-', type='-', url='', + version=None, canAccess=False) node = self.view.nodeView url = node is not None and node.getUrlForTarget(obj) or '' view = self.view.getViewForObject(obj) diff --git a/organize/work/browser.py b/organize/work/browser.py index 1d7a0f2..b3b2a71 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -170,6 +170,9 @@ class WorkItemDetails(TrackDetails): @Lazy def allowedToEditWorkItem(self): #if checkPermission('loops.ManageSite', self.object): + if (self.object is None and + checkPermission('zope.ManageContent', self.view.node)): + return True if checkPermission('zope.ManageContent', self.object): return True return self.user['object'] == getPersonForUser(self.object, self.view.request) diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 0d67bbc..26bcfb0 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -36,8 +36,9 @@ 20:00 2:30 - Task + Task John From 3a6d6a7dddb135c1f743c3c16e9bfda5d07c28f3 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 29 May 2014 11:33:35 +0200 Subject: [PATCH 029/269] make answer options configurable --- knowledge/survey/browser.py | 42 ++++++++++++++++++++++++++------- knowledge/survey/interfaces.py | 20 ++++++++++++++++ knowledge/survey/view_macros.pt | 21 +++++++---------- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 10f90cf..b4d9f06 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -55,6 +55,21 @@ class SurveyView(ConceptView): if self.editable: return 'index.html' + @Lazy + def answerOptions(self): + opts = self.adapted.answerOptions + if not opts: + opts = [ + dict(value='none', label=u'No answer', + description=u'survey_value_none'), + dict(value=3, label=u'Fully applies', + description=u'survey_value_3'), + dict(value=2, label=u'', description=u'survey_value_2'), + dict(value=1, label=u'', description=u'survey_value_1'), + dict(value=0, label=u'Does not apply', + description=u'survey_value_0'),] + return opts + @Lazy def showFeedbackText(self): sft = self.adapted.showFeedbackText @@ -77,7 +92,6 @@ class SurveyView(ConceptView): return False def getTeamData(self, respManager): - #result = [myResponse] result = [] pred = self.conceptManager.get('ismember') if pred is None: @@ -119,7 +133,8 @@ class SurveyView(ConceptView): if value != 'none': uid = key[len('question_'):] question = adapted(self.getObjectForUid(uid)) - value = int(value) + if value.isdigit(): + value = int(value) data[uid] = value response.values[question] = value values = response.getGroupedResult() @@ -167,7 +182,8 @@ class SurveyView(ConceptView): text = qugroup.description info = None if qugroup.minAnswers in (u'', None): - info = translate(_(u'Please answer all questions.'), target_language=lang) + info = translate(_(u'Please answer all questions.'), + target_language=lang) elif qugroup.minAnswers > 0: info = translate(_(u'Please answer at least $minAnswers questions.', mapping=dict(minAnswers=qugroup.minAnswers)), @@ -182,10 +198,19 @@ class SurveyView(ConceptView): self.data = Responses(self.context).load() if self.data: setting = self.data.get(question.uid) - noAnswer = [dict(value='none', checked=(setting == None), - radio=(not question.required))] - return noAnswer + [dict(value=i, checked=(setting == i), radio=True) - for i in reversed(range(question.answerRange))] + if setting is None: + setting = 'none' + setting = str(setting) + result = [] + for opt in self.answerOptions: + value = str(opt['value']) + result.append(dict(value=value, checked=(setting == value))) + return result + + #noAnswer = [dict(value='none', checked=(setting == None), + # radio=(not question.required))] + #return noAnswer + [dict(value=i, checked=(setting == i), radio=True) + # for i in reversed(range(question.answerRange))] class SurveyCsvExport(NodeView): @@ -198,7 +223,8 @@ class SurveyCsvExport(NodeView): @Lazy def questions(self): result = [] - for idx1, qug in enumerate(adapted(self.virtualTargetObject).questionGroups): + for idx1, qug in enumerate( + adapted(self.virtualTargetObject).questionGroups): for idx2, qu in enumerate(qug.questions): result.append((idx1, idx2, qug, qu)) return result diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index 9799865..40f1fed 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -39,6 +39,26 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): default=4, required=True) + answerOptions = Records( + title=_(u'Answer Options'), + description=_(u'Values to select from with corresponding column ' + u'labels and descriptions. There should be at ' + u'least answer range items with numeric values.'), + default=[], + required=False) + + answerOptions.column_types = [ + schema.Text(__name__='value', title=u'Value',), + schema.Text(__name__='label', title=u'Label'), + schema.Text(__name__='description', title=u'Description'),] + + noGrouping = schema.Bool( + title=_(u'No Grouping of Questions'), + description=_(u'The questions should be presented in a linear manner, ' + u'not grouped by categories or question groups.'), + default=False, + required=False) + feedbackColumns = Records( title=_(u'Feedback Columns'), description=_(u'Column definitions for the results table ' diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 0f5d1c9..91be3c1 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -9,6 +9,7 @@ +

Feedback

+ tal:content="structure python: + item.renderText(footer, 'text/restructured')" />
+

Questionnaire

@@ -68,13 +71,10 @@
- No answer - Fully applies - Does not apply + @@ -83,16 +83,11 @@ tal:repeat="value python:item.getValues(question)"> - *** - From 0c043434e202aeccce073fbe2f87281599288656 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 29 May 2014 11:38:41 +0200 Subject: [PATCH 030/269] take mouseover title from answer option description --- knowledge/survey/browser.py | 8 ++------ knowledge/survey/view_macros.pt | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index b4d9f06..04a46d1 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -204,13 +204,9 @@ class SurveyView(ConceptView): result = [] for opt in self.answerOptions: value = str(opt['value']) - result.append(dict(value=value, checked=(setting == value))) + result.append(dict(value=value, checked=(setting == value), + title=opt['description'])) return result - - #noAnswer = [dict(value='none', checked=(setting == None), - # radio=(not question.required))] - #return noAnswer + [dict(value=i, checked=(setting == i), radio=True) - # for i in reversed(range(question.answerRange))] class SurveyCsvExport(NodeView): diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 91be3c1..8f5ec42 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -87,7 +87,7 @@ name string:question_${question/uid}; value value/value; checked value/checked; - title string:survey_value_${value/value}" /> + title value/title" /> From 391bc395f0bee625616765922d7a34b94911adce Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 1 Jun 2014 11:06:13 +0200 Subject: [PATCH 031/269] fix printer settings --- browser/skin/lobo/print.css | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/browser/skin/lobo/print.css b/browser/skin/lobo/print.css index 19dd977..7ea21f3 100644 --- a/browser/skin/lobo/print.css +++ b/browser/skin/lobo/print.css @@ -11,8 +11,14 @@ body { display: none; } +.container { + width: auto; + margin: 0; + border: 0; +} + #content { -/* width: 100%; */ - width: 80%; + width: auto; color: Black; } + From 95a510c7592ab8aa4d4a41020e93dbfaf4ca1aa5 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 3 Jun 2014 18:14:57 +0200 Subject: [PATCH 032/269] allow for presentation of questions without grouping by question groups --- knowledge/survey/browser.py | 21 +++++++++++++++++++++ knowledge/survey/view_macros.pt | 15 +++++++++------ locales/de/LC_MESSAGES/loops.mo | Bin 24851 -> 25619 bytes locales/de/LC_MESSAGES/loops.po | 17 ++++++++++++++++- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 04a46d1..205ad5b 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -44,6 +44,7 @@ class SurveyView(ConceptView): data = None errors = None + minBatchSize = 10 @Lazy def macro(self): @@ -55,6 +56,26 @@ class SurveyView(ConceptView): if self.editable: return 'index.html' + @Lazy + def groups(self): + result = [] + if self.adapted.noGrouping: + questions = list(self.adapted.questions) + questions.sort(key=lambda x: x.title) + size = len(questions) + nb = size / self.minBatchSize + rem = size % self.minBatchSize + bs = self.minBatchSize + ((rem + self.minBatchSize) / nb) + for idx in range(0, size, bs): + result.append(dict(title=u'Question', infoText=None, + questions=questions[idx:idx+bs])) + else: + for group in self.adapted.questionGroups: + result.append(dict(title=group.title, + infoText=self.getInfoText(group), + questions=group.questions)) + return result + @Lazy def answerOptions(self): opts = self.adapted.answerOptions diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 8f5ec42..cd14558 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -61,11 +61,14 @@
- - + + + + - + tal:repeat="question group/questions"> - +
 
  
- + +
@@ -77,7 +80,7 @@ tal:content="opt/label" />
@@ -90,7 +93,7 @@ title value/title" />
diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 26ec710435d2057691fa31ac636944538849dd62..775094eb12c4a6c6afe6b518764a3727b54ff080 100644 GIT binary patch delta 9863 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 diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index a478092..301c025 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.0\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2014-05-25 12:00 CET\n" +"PO-Revision-Date: 2014-06-03 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -178,6 +178,18 @@ msgstr "Glossareintrag anlegen." msgid "Answer Range" msgstr "Abstufung Bewertungen" +msgid "Answer Options" +msgstr "Antwortmöglichkeiten" + +msgid "Values to select from with corresponding column labels and descriptions. There should be at least answer range items with numeric values." +msgstr "Auszuwählende Werte mit zugehörigen Spaltenüberschriften und Beschreibungen. Es sollte mindestens so viele Einträge mit numerischen Werten geben wie durch das Feld 'Abstufung Bewertungen' vorgegeben." + +msgid "No Grouping of Questions" +msgstr "Keine Gruppierung der Fragen" + +msgid "The questions should be presented in a linear manner, not grouped by categories or question groups." +msgstr "Die Fragen sollen in linearer Reihenfolge ausgegeben und nicht nach Fragengruppen bzw. Kategorien gruppiert werden." + msgid "Feedback Footer" msgstr "Auswertungs-Hinweis" @@ -205,6 +217,9 @@ msgstr "Negative Polarität" msgid "Value inversion: High selection means low value." msgstr "Invertierung der Bewertung: Hohe gewählte Stufe bedeutet niedriger Wert." +msgid "Question" +msgstr "Frage" + msgid "Questionnaire" msgstr "Fragebogen" From ed996977f9a05947a28e1fb9c43ede7ceee7dee1 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 4 Jun 2014 08:57:34 +0200 Subject: [PATCH 033/269] highlight missing questions; show team size --- knowledge/survey/browser.py | 21 ++++++++++++---- knowledge/survey/view_macros.pt | 41 +++++++++++++++++++------------- locales/de/LC_MESSAGES/loops.mo | Bin 25619 -> 25793 bytes locales/de/LC_MESSAGES/loops.po | 6 +++++ 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 205ad5b..1ca783c 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -45,6 +45,7 @@ class SurveyView(ConceptView): data = None errors = None minBatchSize = 10 + teamData = None @Lazy def macro(self): @@ -170,8 +171,8 @@ class SurveyView(ConceptView): score=int(round(r['score'] * 100)), rank=r['rank']) for r in values] if self.showTeamResults: - teamData = self.getTeamData(respManager) - teamValues = response.getTeamResult(values, teamData) + self.teamData = self.getTeamData(respManager) + teamValues = response.getTeamResult(values, self.teamData) for idx, r in enumerate(teamValues): result[idx]['average'] = int(round(r['average'] * 100)) result[idx]['teamRank'] = r['rank'] @@ -182,7 +183,8 @@ class SurveyView(ConceptView): values = response.values for qu in self.adapted.questions: if qu.required and qu not in values: - errors.append('Please answer the obligatory questions.') + errors.append(dict(uid=qu.uid, + text='Please answer the obligatory questions.')) break qugroups = {} for qugroup in self.adapted.questionGroups: @@ -194,7 +196,12 @@ class SurveyView(ConceptView): if minAnswers in (u'', None): minAnswers = len(qugroup.questions) if count < minAnswers: - errors.append('Please answer the minimum number of questions.') + if self.adapted.noGrouping: + errors.append(dict(uid=qugroup.uid, + text='Please answer the highlighted questions.')) + else: + errors.append(dict(uid=qugroup.uid, + text='Please answer the minimum number of questions.')) break return errors @@ -229,6 +236,12 @@ class SurveyView(ConceptView): title=opt['description'])) return result + def getCssClass(self, question): + cls = '' + if self.errors and self.data.get(question.uid) is None: + cls = 'error ' + return cls + 'vpad' + class SurveyCsvExport(NodeView): diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index cd14558..1a32b32 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -37,9 +37,15 @@ +

+ Team Size: +
  +

@@ -56,7 +62,7 @@ tal:condition="errors">
+ tal:content="error/text" />
@@ -79,20 +85,21 @@ i18n:translate="" tal:content="opt/label" /> - - - - - - + + + + + + + + 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 delta 8607 zcmYk>3!INt{=o5P!nn^EBQp$U%(&k#gNVxg7AlvB#y#muCA4dw5D_JQXtOE0kV=$Y zODTHMU;eiKE39tXtaDc+?k$RCRm!R4)y0Y0g6z5_eJc@Q$r%@D@!-i(q{1J3(H=#$e9c{lKJ%VFs{m;>z_#WM%xO|JG!@6k0wrGbv&@CQ@u5@a6 ze<#|}eQ3w2P`)0kkbgOp@5V~x-wyfr&?Ei=omg7S(jAGTEE0B0T_xW2G-xO{=6q zHddv)Zm>DlBi|XlbR*F_b5C#{I^YArW$3^w(Fr~t@*6ST@BbwdcCZ6I`#tCiKR{>t zH*|t0&>cC0)~nb$iY8$ZdSr{xBU^)+xCL#u8-0Hm-NDb%1^pZ23=);vB%fI=v_V_6 zymu%chE>Slf;OCuUfTQ7TfGilz(#b1Td;JC(IeW0)_)7#iBE!GwPF9=svjt@JCuKb)yPNflJ{A`T4=rc=+3r6CtBQ&{db^l z6u6Sx(9dE9dV80ke=4rVmbeYs{pb^HjMdunCmddad3ZhA@odb*dFVt}g!~$G0?(oK zw#FpJ<3@TEtyJh!JT<(J(UmSjf5KOV^3CYhZx8uB z=mO#cq2M$+;90bT^XN>gbVzolI(nA1&!!zjCokIt%&?zw+J%W13tEdG!;6!ww8R(sv zg>Ll*6_QqGbaQ=L<{ADazNjtRRC1}I$=mh(q9ghk5DQLYp=n*W$ z7Pu1g@eTA2e1g{dGUQLAm+%a_gO$6o|E@5zYf>=>ZCHTLv@tq?wpbJUq7x}WJD!Rj z!5wJ3S=bF@tc9{2mw6Y)%z0EDrACSvJelU6jV?+5YbcYt9 z6L=7vKnh*q8g!t|SOd4C{T&SNKSalgzae2q=fjJ#mn9R)K`S&3`7Y>+u0(g>26W)* z!I|iQbI^$_L|6PE+W#u_j;%r4ZA6YBjy8uEFQFA*M;q=99t<8vC-fn@V_%2zAA{$@ z`}E6`iR57EQKCoK811)Pc;CA;A4k#P@L~iyff6kJj?v3C1N|kNA6yY!AKZ%8-;Mrm z970!m9Nm$V=tTaB_J0<;@UN}&*ej->OZUTF~?GkWeH%*$K+DgfTbr`W3ZD%Lm0IT*;(RVK(NIUygS85<0+MwBcXS z`rn4~a($Ecx#-H9qg&h=J<0)CAIG5+pNEZcEjGaTO%kr~B<5j-eo4g!SdaYWXoC`T z0t?Z4>(DLSg?9Wtx@aTi;W%8yEGju-~x1qA3_!yM~{*yN5M`^$KB{k{s+t8 z5wziZ=*m7pzmo6JOL!j3W9ERQ;~cboAr@d8wEwHo30@oC-;R|SKbk|rty+Wy_&Bz%4^ED>_F(qk z85U8X?XVELV?&&Tp8W%8hbeR=Yl2Up6MP!ID_hZt{2p!hdT>8Ffj^@Y&KQ!^uQ7!E zcZ+i=a3|WJXVVk!#uCiLqv#QQj;{P?^sC9dDmmMhXg__>&$a|Ja2k3fad^Kvynhk> ziuU-zfj`2kcnaNt|DY=_xH{Rg#^}JU(UtbZD%cmjY{Ss@)6ogXn1#!*8m>oI{xbH( z9XJN#=$hp6T#s&fjGoam=n6JtbKHfl!95;puC-SX4u7Dm@54Jx7y^3i(D&Sljy{&j73ToQ}EVXQ7vDIXZzC(FweQZuQ&f zcYiqK|AG$i5oY6wkUtZQMkNcZFpB+mYqBV?VjkMCHQI3}?1bIW0cW5CEy7}4g8m`3 z2fN^3@lLFL9si*jAHt^i33|CJk4|>7ExN;9M|1uT)SCjgY#@3BbI|go=+>{mEL@B3 z&=$1bc635-q8+}4ex8Rg509d^{WQAJG`>0ORYuEe#3cM7X@LE)2Oh+w=uV8hKKW@L zhaS-kw4?dx`v=hptU^2fEe^r;*aJ_a3n?y1Cfo(R{r%B}#e+$ByGLOz-hg%#qbpp5 z9q|#Yi|>T@U!xuTfDYidY(q_#FO`u+AtcW5j+ksHw!#%Q~R;r;TEUxQBI8FWG0L-}5;%J|VcB%J9-=;iqW z-O`i6Z_yq45uH%_gv1PVt81cXT8K`dG1^Z%^wM@jC)f{dKP2R@#n_pZkZ^$8gE4v( z%g~9fMb0YPhz__rlpn-ep$-3vPUIB2r9Y!9uaMeVyQo8rH2!Kr&R>j~r0Wy6ldex(P5KP_ zI3ZD$K&|#N(80B*qXv9SME`qgRQ-(lVCQo5Z6ObiyXYT4Ee|pDfaS ziRPrevhzr9!V0Nc_43MHMDA>=bG^p#KPX#3JWWYc^oPQy4DkqMQ-~W$Prx^Q!Ly9u z51Qx!%Kn8-2%j$!qREu|Umk8C77#uw6QVoF&nM=RukF|OM-me$IEb5xlf;E*4dqM7 z+=K)1mQ>aJym&0Rb;Nq&)(cf}WJu@YY+?lOFFeCZl!W~9UzDel&L$@Fer_nQh0BNn zq7m@|@75Cjgzn2{P)ke{|PHM^j3w^Nk&CXNurylX}bCi;;7 zC$W@RLA0l=Bk?-vTL^zoFH0RS$cx8QI)}K8@TrV9;OLO|C;tybCGzbt4Tlhmhy&#B zAO?{xA}%~XlemJoGL&t&@B#~o?}??ntDIcU Date: Tue, 17 Jun 2014 14:40:33 +0200 Subject: [PATCH 034/269] allow editing of track data in management interface --- organize/tracking/browser.py | 28 ++++++++++++ organize/tracking/configure.zcml | 7 +++ organize/tracking/edit_track.pt | 73 ++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 organize/tracking/edit_track.pt diff --git a/organize/tracking/browser.py b/organize/tracking/browser.py index 33e82bb..cbe486d 100644 --- a/organize/tracking/browser.py +++ b/organize/tracking/browser.py @@ -21,9 +21,11 @@ View classes for tracks. """ from zope import component +from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.security.interfaces import IAuthentication, PrincipalLookupError from zope.cachedescriptors.property import Lazy from zope.app.pagetemplate import ViewPageTemplateFile +from zope.security.proxy import removeSecurityProxy from zope.traversing.browser import absoluteURL from zope.traversing.api import getName @@ -34,6 +36,8 @@ from loops.browser.form import ObjectForm, EditObject from loops.organize.party import getPersonForUser from loops import util +track_edit_template = ViewPageTemplateFile('edit_track.pt') + class BaseTrackView(TrackView): @@ -102,6 +106,30 @@ class BaseTrackView(TrackView): return self.request.principal.id +class EditForm(BaseTrackView): + + template = track_edit_template + + def update(self): + form = self.request.form + if not form.get('form_submitted'): + return True + data = {} + print '*** update', form + for row in form.get('data') or []: + key = row['key'] + if not key: + continue + value = row['value'] + # TODO: unmarshall value if necessary + data[key] = value + context = removeSecurityProxy(self.context) + context.data = data + return True + + +# specialized views + class ChangeView(BaseTrackView): pass diff --git a/organize/tracking/configure.zcml b/organize/tracking/configure.zcml index 6c81981..016ddf5 100644 --- a/organize/tracking/configure.zcml +++ b/organize/tracking/configure.zcml @@ -82,6 +82,13 @@ class="loops.organize.tracking.browser.ChangeView" permission="zope.View" /> + + + + + +
+ + +

Edit Track

+
+ + + + + + + + + + + + + + + + +
Task:
Run:
User:
Timestamp:
:
+
+

Data

+
+ + + + + + + + + + + + + +
KeyValue
+ +
+ +
+
+
+ +
+
+
+ +
+ + + + From 2f74920692814ce93c7622d8b1630f44eaecf1f5 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 17 Jun 2014 17:14:17 +0200 Subject: [PATCH 035/269] survey CSV export: fix encoding, add institution column --- knowledge/survey/browser.py | 18 ++++++++++++------ organize/tracking/browser.py | 1 - 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 1ca783c..0607ca3 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -31,7 +31,7 @@ from cybertools.knowledge.survey.questionnaire import Response from cybertools.util.date import formatTimeStamp from loops.browser.concept import ConceptView from loops.browser.node import NodeView -from loops.common import adapted +from loops.common import adapted, baseObject from loops.knowledge.survey.response import Responses from loops.organize.party import getPersonForUser from loops.util import getObjectForUid @@ -248,7 +248,7 @@ class SurveyCsvExport(NodeView): encoding = 'ISO8859-15' def encode(self, text): - text.encode(self.encoding) + return text.encode(self.encoding) @Lazy def questions(self): @@ -261,24 +261,30 @@ class SurveyCsvExport(NodeView): @Lazy def columns(self): - infoCols = ['Name', 'Timestamp'] + infoCols = ['Institution', 'Name', 'Timestamp'] dataCols = ['%02i-%02i' % (item[0], item[1]) for item in self.questions] return infoCols + dataCols def getRows(self): + memberPred = self.conceptManager.get('ismember') for tr in Responses(self.virtualTargetObject).getAllTracks(): p = adapted(getObjectForUid(tr.userName)) - name = p and p.title or u'???' + name = self.encode(p and p.title or u'???') + inst = u'' + if memberPred is not None: + for i in baseObject(p).getParents([memberPred]): + inst = self.encode(i.title) + break ts = formatTimeStamp(tr.timeStamp) cells = [tr.data.get(qu.uid, -1) for (idx1, idx2, qug, qu) in self.questions] - yield [name, ts] + cells + yield [inst, name, ts] + cells def __call__(self): f = StringIO() writer = csv.writer(f, delimiter=',') writer.writerow(self.columns) - for row in self.getRows(): + for row in sorted(self.getRows()): writer.writerow(row) text = f.getvalue() self.setDownloadHeader(text) diff --git a/organize/tracking/browser.py b/organize/tracking/browser.py index cbe486d..7e2cd7a 100644 --- a/organize/tracking/browser.py +++ b/organize/tracking/browser.py @@ -115,7 +115,6 @@ class EditForm(BaseTrackView): if not form.get('form_submitted'): return True data = {} - print '*** update', form for row in form.get('data') or []: key = row['key'] if not key: From 6dde1461891e1e41b58cd53032444e972402dcd3 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 29 Jun 2014 14:08:14 +0200 Subject: [PATCH 036/269] provide a 'password reset' form to change/set a forgotten password --- organize/README.txt | 17 +++++++ organize/browser/configure.zcml | 6 +++ organize/browser/member.py | 82 +++++++++++++++++++++++++++++++-- organize/interfaces.py | 8 ++++ 4 files changed, 109 insertions(+), 4 deletions(-) diff --git a/organize/README.txt b/organize/README.txt index c80f403..c37d7f3 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -228,6 +228,23 @@ We need a principal for testing the login stuff: >>> pwcView.update() False +Reset Password +-------------- + +Invalidates the user account by generating a new password. A mail ist sent to +the email address of the person with a link for re-activating the account +and enter a new password. + + >>> data = {'loginName': u'dummy', + ... 'action': 'update'} + + >>> request = TestRequest(form=data) + + >>> from loops.organize.browser.member import PasswordReset + >>> pwrView = PasswordReset(menu, request) + >>> pwrView.update() + True + Pure Person-based Authentication ================================ diff --git a/organize/browser/configure.zcml b/organize/browser/configure.zcml index e351efa..3548bed 100644 --- a/organize/browser/configure.zcml +++ b/organize/browser/configure.zcml @@ -45,6 +45,12 @@ class="loops.organize.browser.member.PasswordChange" permission="zope.View" /> + + Date: Mon, 7 Jul 2014 12:41:58 +0200 Subject: [PATCH 037/269] allow editing of workitems on report --- expert/browser/results.pt | 11 +++++++++++ expert/field.py | 15 ++++++++++++++- organize/work/README.txt | 2 +- organize/work/report.py | 3 ++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/expert/browser/results.pt b/expert/browser/results.pt index e64f96f..748dfc0 100644 --- a/expert/browser/results.pt +++ b/expert/browser/results.pt @@ -80,6 +80,17 @@ + + + + + + + + +
+
+ +
+ + +
@@ -238,7 +249,9 @@ Start - End: - - + + + Duration/Effort: From 527c2f445d69ac77378b2cdb55836806c5d5e830 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 8 Jul 2014 07:59:28 +0200 Subject: [PATCH 039/269] include fields for start and end dates on work statement report --- knowledge/qualification/report.py | 3 ++- organize/work/configure.zcml | 2 +- organize/work/report.py | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/knowledge/qualification/report.py b/knowledge/qualification/report.py index 3c72556..90a584c 100644 --- a/knowledge/qualification/report.py +++ b/knowledge/qualification/report.py @@ -26,6 +26,7 @@ from cybertools.util.jeep import Jeep from loops.expert.report import ReportInstance from loops.organize.work.report import WorkRow from loops.organize.work.report import deadline, day, task, party, state +from loops.organize.work.report import dayStart, dayEnd from loops.organize.work.report import workTitle, workDescription from loops import util @@ -37,7 +38,7 @@ class QualificationOverview(ReportInstance): rowFactory = WorkRow - fields = Jeep((task, party, workTitle, day, state)) # +deadline? + fields = Jeep((task, party, workTitle, dayStart, dayEnd, state)) # +deadline? defaultOutputFields = fields diff --git a/organize/work/configure.zcml b/organize/work/configure.zcml index bbd83f7..17dac6c 100644 --- a/organize/work/configure.zcml +++ b/organize/work/configure.zcml @@ -97,7 +97,7 @@ diff --git a/organize/work/report.py b/organize/work/report.py index 0691da5..1da2e3f 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -98,6 +98,14 @@ day = TrackDateField('day', u'Day', description=u'The day the work was done.', cssClass='center', executionSteps=['sort', 'output']) +dayStart = TrackDateField('dayStart', u'Start Day', + description=u'The day the unit of work was started.', + cssClass='center', + executionSteps=['sort', 'output']) +dayEnd = TrackDateField('dayEnd', u'End Day', + description=u'The day the unit of work was finished.', + cssClass='center', + executionSteps=['sort', 'output']) timeStart = TrackTimeField('start', u'Start', description=u'The time the unit of work was started.', executionSteps=['sort', 'output']) @@ -145,6 +153,12 @@ class WorkRow(BaseRow): def getDay(self, attr): return self.context.timeStamp + def getStart(self, attr): + return self.context.start + + def getEnd(self, attr): + return self.context.end + def getDuration(self, attr): value = self.context.data.get('duration') if value is None: @@ -157,7 +171,9 @@ class WorkRow(BaseRow): value = self.getDuration(attr) return value - attributeHandlers = dict(day=getDay, dayFrom=getDay, dayTo=getDay, + attributeHandlers = dict(day=getDay, + dayStart=getStart, dayEnd=getEnd, + dayFrom=getDay, dayTo=getDay, duration=getDuration, effort=getEffort) From e675a9587186e3b598ce1e0aa2f7c00578d14401 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 8 Jul 2014 16:27:06 +0200 Subject: [PATCH 040/269] have a fixed batch size of 12 --- knowledge/survey/browser.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 0607ca3..93119d8 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -44,7 +44,7 @@ class SurveyView(ConceptView): data = None errors = None - minBatchSize = 10 + batchSize = 12 teamData = None @Lazy @@ -64,9 +64,8 @@ class SurveyView(ConceptView): questions = list(self.adapted.questions) questions.sort(key=lambda x: x.title) size = len(questions) - nb = size / self.minBatchSize - rem = size % self.minBatchSize - bs = self.minBatchSize + ((rem + self.minBatchSize) / nb) + #nb, rem = divmod(size, self.batchSize) + bs = self.batchSize for idx in range(0, size, bs): result.append(dict(title=u'Question', infoText=None, questions=questions[idx:idx+bs])) From 50b45b2c568dbffe573aaddfac8fbc3a9edd916f Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 11 Jul 2014 08:38:31 +0200 Subject: [PATCH 041/269] provide report for listing qualifications for a competence --- knowledge/qualification/configure.zcml | 12 ++++++++ knowledge/qualification/report.py | 39 ++++++++++++++++++++++++- locales/de/LC_MESSAGES/loops.mo | Bin 25793 -> 25863 bytes locales/de/LC_MESSAGES/loops.po | 8 ++++- 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index a6ca61b..f5f1487 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -37,4 +37,16 @@ set_schema="loops.expert.report.IReportInstance" /> + + + + + + diff --git a/knowledge/qualification/report.py b/knowledge/qualification/report.py index 90a584c..b7991eb 100644 --- a/knowledge/qualification/report.py +++ b/knowledge/qualification/report.py @@ -33,7 +33,7 @@ from loops import util class QualificationOverview(ReportInstance): - type = "qualification_overview" + type = 'qualification_overview' label = u'Qualification Overview' rowFactory = WorkRow @@ -60,3 +60,40 @@ class QualificationOverview(ReportInstance): for wi in workItems.query(taskId=uid, state=self.states): result.append(wi) return result + + +class Qualifications(QualificationOverview): + + type = 'qualifications' + label = u'Qualifications' + + taskTypeNames = ('competence',) + + def getOptions(self, option): + return self.view.typeOptions(option) + + def selectObjects(self, parts): + result = [] + workItems = self.recordManager['work'] + target = self.view.context + tasks = [target] + self.getAllSubtasks(target) + for t in tasks: + uid = util.getUidForObject(t) + for wi in workItems.query(taskId=uid, state=self.states): + result.append(wi) + return result + + def getAllSubtasks(self, concept): + result = [] + for c in concept.getChildren([self.view.defaultPredicate]): + if c.conceptType in self.taskTypes: + result.append(c) + result.extend(self.getAllSubtasks(c)) + return result + + @Lazy + def taskTypes(self): + return [c for c in [self.conceptManager.get(name) + for name in self.taskTypeNames] + if c is not None] + diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index b7a08a183d6b3d77dfc1e945f4f78f49dd174b97..a98c638d83fc71ebaa3c0457ccd896eef5eb0515 100644 GIT binary patch delta 9280 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 delta 9257 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 diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index d64568b..0b403e5 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.0\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2014-06-03 12:00 CET\n" +"PO-Revision-Date: 2014-07-11 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -849,6 +849,12 @@ msgstr "Beginn" msgid "End date" msgstr "Ende" +msgid "Start Day" +msgstr "Beginn" + +msgid "End Day" +msgstr "Ende" + msgid "Knowledge" msgstr "Kompetenzen" From d5c851a5de412adf27fb1bd3f6c669bf4ddd69a5 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 11 Jul 2014 11:23:06 +0200 Subject: [PATCH 042/269] add qualification report for person; sorting; add action 'start' to work item type 'checkup' --- expert/field.py | 6 ++++++ knowledge/qualification/configure.zcml | 12 ++++++++++++ knowledge/qualification/report.py | 19 +++++++++++++++++++ organize/work/report.py | 2 +- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/expert/field.py b/expert/field.py index 9448d0f..4bf1ae6 100644 --- a/expert/field.py +++ b/expert/field.py @@ -216,6 +216,12 @@ class RelationField(Field): class TargetField(RelationField): + def getSortValue(self, row): + value = self.getRawValue(row) + if value is not None: + value = util.getObjectForUid(value) + return value.title + def getValue(self, row): value = self.getRawValue(row) if value is None: diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index f5f1487..e4f02c9 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -49,4 +49,16 @@ set_schema="loops.expert.report.IReportInstance" /> + + + + + + diff --git a/knowledge/qualification/report.py b/knowledge/qualification/report.py index b7991eb..7849b70 100644 --- a/knowledge/qualification/report.py +++ b/knowledge/qualification/report.py @@ -41,6 +41,8 @@ class QualificationOverview(ReportInstance): fields = Jeep((task, party, workTitle, dayStart, dayEnd, state)) # +deadline? defaultOutputFields = fields + defaultSortCriteria = (party, task,) + def getOptions(self, option): return self.view.options(option) @@ -97,3 +99,20 @@ class Qualifications(QualificationOverview): for name in self.taskTypeNames] if c is not None] + +class PersonQualifications(QualificationOverview): + + type = 'person_qualifications' + label = u'Qualifications for Person' + + defaultSortCriteria = (task,) + + def getOptions(self, option): + return self.view.typeOptions(option) + + def selectObjects(self, parts): + workItems = self.recordManager['work'] + person = self.view.context + uid = util.getUidForObject(person) + return workItems.query(userName=uid, state=self.states) + diff --git a/organize/work/report.py b/organize/work/report.py index 1da2e3f..e13ac3d 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -114,7 +114,7 @@ timeEnd = TrackTimeField('end', u'End', executionSteps=['output']) task = TargetField('taskId', u'Task', description=u'The task to which work items belong.', - executionSteps=['output']) + executionSteps=['sort', 'output']) party = TargetField('userName', u'Party', description=u'The party (usually a person) who did the work.', fieldType='selection', From e5b4e9800e0b9d3c999f339920bb2548dd604526 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 12 Jul 2014 08:42:41 +0200 Subject: [PATCH 043/269] report view variant to be used as a part of a compound view --- expert/browser/configure.zcml | 8 ++++++++ expert/browser/report.py | 11 +++++++++++ expert/browser/results.pt | 10 ++++++++++ 3 files changed, 29 insertions(+) diff --git a/expert/browser/configure.zcml b/expert/browser/configure.zcml index 5dadb3a..e5804f2 100644 --- a/expert/browser/configure.zcml +++ b/expert/browser/configure.zcml @@ -91,4 +91,12 @@ factory="loops.expert.browser.report.ResultsConceptView" permission="zope.View" /> + + diff --git a/expert/browser/report.py b/expert/browser/report.py index 002a950..acf6279 100644 --- a/expert/browser/report.py +++ b/expert/browser/report.py @@ -191,6 +191,17 @@ class ResultsConceptView(ConceptView): return self.result_macros[col.renderer] +class EmbeddedResultsConceptView(ResultsConceptView): + + @Lazy + def macro(self): + return self.result_macros['embedded_content'] + + @Lazy + def title(self): + return self.report.title + + class ReportConceptView(ResultsConceptView, ReportView): """ View on a concept using a report. """ diff --git a/expert/browser/results.pt b/expert/browser/results.pt index 748dfc0..b9d9ed1 100644 --- a/expert/browser/results.pt +++ b/expert/browser/results.pt @@ -25,6 +25,16 @@
+
+
+ +
+
+
+ +

Date: Sat, 12 Jul 2014 16:25:08 +0200 Subject: [PATCH 044/269] fix work item form: missing div end tag --- organize/work/work_macros.pt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 54d9bb0..5a991aa 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -182,7 +182,7 @@ tal:attributes="value view/date" /> + tal:attributes="value view/endDate" />
Task: -
+ From f0248b2ec836cac4a7e27db2bdd518f7dee229e0 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 31 Jul 2014 18:45:07 +0200 Subject: [PATCH 047/269] work in progress: job positions and competences --- expert/README.txt | 6 +- expert/search.txt | 16 ++--- external/README.txt | 6 +- knowledge/data/knowledge_de.dmp | 12 +++- knowledge/data/knowledge_update_de.dmp | 14 +++- knowledge/qualification/browser.py | 62 +++++++++++++++++- knowledge/qualification/configure.zcml | 16 +++++ .../qualification/qualification_macros.pt | 17 +++++ locales/de/LC_MESSAGES/loops.mo | Bin 25863 -> 26048 bytes locales/de/LC_MESSAGES/loops.po | 13 +++- organize/README.txt | 2 +- system/sync/README.txt | 2 +- xmlrpc/README.txt | 15 +++-- 13 files changed, 154 insertions(+), 27 deletions(-) create mode 100644 knowledge/qualification/qualification_macros.pt diff --git a/expert/README.txt b/expert/README.txt index 545c865..7ac8ed8 100644 --- a/expert/README.txt +++ b/expert/README.txt @@ -27,7 +27,7 @@ configuration): >>> concepts, resources, views = t.setup() >>> len(concepts) + len(resources) - 38 + 41 >>> loopsRoot = site['loops'] @@ -47,11 +47,11 @@ Type- and text-based queries >>> from loops.expert import query >>> qu = query.Title('ty*') >>> list(qu.apply()) - [0, 2, 69] + [0, 2, 79] >>> qu = query.Type('loops:*') >>> len(list(qu.apply())) - 38 + 41 >>> qu = query.Type('loops:concept:predicate') >>> len(list(qu.apply())) diff --git a/expert/search.txt b/expert/search.txt index d66ed00..8905fc6 100755 --- a/expert/search.txt +++ b/expert/search.txt @@ -66,13 +66,13 @@ zcml in real life: >>> t = searchView.typesForSearch() >>> len(t) - 16 + 19 >>> t.getTermByToken('loops:resource:*').title 'Any Resource' >>> t = searchView.conceptTypesForSearch() >>> len(t) - 13 + 16 >>> t.getTermByToken('loops:concept:*').title 'Any Concept' @@ -91,7 +91,7 @@ a controller attribute for the search view. >>> searchView.submitReplacing('1.results', '1.search.form', pageView) 'submitReplacing("1.results", "1.search.form", - "http://127.0.0.1/loops/views/page/.target100/@@searchresults.html");...' + "http://127.0.0.1/loops/views/page/.target110/@@searchresults.html");...' Basic (text/title) search ------------------------- @@ -177,7 +177,7 @@ of the concepts' titles: >>> request = TestRequest(form=form) >>> view = Search(page, request) >>> view.listConcepts() - u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '105'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '107'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '109'}]}" + u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '115'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '117'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '119'}]}" Preset Concept Types on Search Forms ------------------------------------ @@ -219,13 +219,13 @@ and thus include the customer type in the preset search types. >>> searchView.conceptsForType('loops:concept:customer') [{'token': 'none', 'title': u'not selected'}, - {'token': '78', 'title': u'Customer 1'}, - {'token': '80', 'title': u'Customer 2'}, - {'token': '82', 'title': u'Customer 3'}] + {'token': '88', 'title': u'Customer 1'}, + {'token': '90', 'title': u'Customer 2'}, + {'token': '92', 'title': u'Customer 3'}] Let's use this new search option for querying: - >>> form = {'search.4.text_selected': u'74'} + >>> form = {'search.4.text_selected': u'84'} >>> resultsView = SearchResults(page, TestRequest(form=form)) >>> results = list(resultsView.results) >>> results[0].title diff --git a/external/README.txt b/external/README.txt index 5fddc0e..c98ad79 100644 --- a/external/README.txt +++ b/external/README.txt @@ -17,7 +17,7 @@ Let's set up a loops site with basic and example concepts and resources. >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> len(concepts), len(resources), len(views) - (35, 3, 1) + (38, 3, 1) Importing loops Objects @@ -44,7 +44,7 @@ Creating the corresponding objects >>> loader = Loader(loopsRoot) >>> loader.load(elements) >>> len(concepts), len(resources), len(views) - (36, 3, 1) + (39, 3, 1) >>> from loops.common import adapted >>> adMyquery = adapted(concepts['myquery']) @@ -118,7 +118,7 @@ Extracting elements >>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export')) >>> elements = list(extractor.extract()) >>> len(elements) - 67 + 74 Writing object information to the external storage -------------------------------------------------- diff --git a/knowledge/data/knowledge_de.dmp b/knowledge/data/knowledge_de.dmp index 83367d9..751cf9f 100644 --- a/knowledge/data/knowledge_de.dmp +++ b/knowledge/data/knowledge_de.dmp @@ -1,6 +1,12 @@ -type(u'competence', u'Kompetenz', viewName=u'', +type(u'competence', u'Qualifikation', viewName=u'', typeInterface=u'loops.knowledge.qualification.interfaces.ICompetence', options=u'action.portlet:create_subtype,edit_concept') +type(u'ipskill', u'Kompetenz', viewName=u'', + options=u'action.portlet:edit_concept') +type(u'ipskillsrequired', u'Soll-Profil', viewName=u'', + options=u'action.portlet:edit_concept') +type(u'jobposition', u'Stelle', viewName=u'', + options=u'action.portlet:edit_concept') type(u'person', u'Person', viewName=u'', typeInterface=u'loops.knowledge.interfaces.IPerson', options=u'action.portlet:createQualification,editPerson') @@ -35,6 +41,9 @@ concept(u'qualification_overview', u'Qualification Overview', u'report', # structure child(u'general', u'competence', u'standard') child(u'general', u'depends', u'standard') +child(u'general', u'ipskill', u'standard') +child(u'general', u'ipskillsrequired', u'standard') +child(u'general', u'jobposition', u'standard') child(u'general', u'knows', u'standard') child(u'general', u'person', u'standard') child(u'general', u'provides', u'standard') @@ -44,6 +53,7 @@ child(u'general', u'topic', u'standard') #child(u'general', u'training', u'standard') child(u'system', u'issubtype', u'standard') +child(u'system', u'report', u'standard') child(u'competence', u'competence', u'issubtype') #child(u'competence', u'training', u'issubtype', usePredicate=u'provides') diff --git a/knowledge/data/knowledge_update_de.dmp b/knowledge/data/knowledge_update_de.dmp index 2a359c2..06d7f29 100644 --- a/knowledge/data/knowledge_update_de.dmp +++ b/knowledge/data/knowledge_update_de.dmp @@ -1,6 +1,14 @@ -type(u'competence', u'Kompetenz', viewName=u'', +type(u'competence', u'Qualifikation', viewName=u'', typeInterface=u'loops.knowledge.qualification.interfaces.ICompetence', options=u'action.portlet:create_subtype,edit_concept') +type(u'ipskill', u'Kompetenz', viewName=u'', + options=u'action.portlet:edit_concept') +type(u'ipskillsrequired', u'Soll-Profil', viewName=u'', + options=u'action.portlet:edit_concept') +type(u'jobposition', u'Stelle', viewName=u'', + options=u'action.portlet:edit_concept') +type(u'report', u'Report', viewName=u'', + typeInterface='loops.expert.report.IReport') # type(u'person', u'Person', viewName=u'', # typeInterface=u'loops.knowledge.interfaces.IPerson', # options=u'action.portlet:editPerson') @@ -33,6 +41,9 @@ concept(u'qualification_overview', u'Qualification Overview', u'report', # structure child(u'general', u'competence', u'standard') child(u'general', u'depends', u'standard') +child(u'general', u'ipskill', u'standard') +child(u'general', u'ipskillsrequired', u'standard') +child(u'general', u'jobposition', u'standard') child(u'general', u'knows', u'standard') #child(u'general', u'person', u'standard') child(u'general', u'provides', u'standard') @@ -42,6 +53,7 @@ child(u'general', u'requires', u'standard') #child(u'general', u'training', u'standard') child(u'system', u'issubtype', u'standard') +child(u'system', u'report', u'standard') child(u'competence', u'competence', u'issubtype') #child(u'competence', u'training', u'issubtype', usePredicate=u'provides') diff --git a/knowledge/qualification/browser.py b/knowledge/qualification/browser.py index e9e6641..054e143 100644 --- a/knowledge/qualification/browser.py +++ b/knowledge/qualification/browser.py @@ -25,11 +25,71 @@ from zope import interface, component from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy +from cybertools.browser.action import actions +from loops.browser.action import DialogAction +from loops.browser.concept import ConceptView from loops.expert.browser.report import ResultsConceptView -from loops.knowledge.browser import template, knowledge_macros +from loops.organize.party import getPersonForUser +from loops.util import _ + +template = ViewPageTemplateFile('qualification_macros.pt') + + +actions.register('createJobPosition', 'portlet', DialogAction, + title=_(u'Create Job...'), + description=_(u'Create a new job / position.'), + viewName='create_concept.html', + dialogName='createPosition', + typeToken='.loops/concepts/jobposition', + fixedType=True, + innerForm='inner_concept_form.html', + permission='loops.AssignAsParent', +) class Qualifications(ResultsConceptView): reportName = 'qualification_overview' + +class QualificationBaseView(object): + + template = template + templateName = 'knowledge.qualification' + + @Lazy + def institutionType(self): + return self.conceptManager['institution'] + + @Lazy + def jobPositionType(self): + return self.conceptManager['jobposition'] + + @Lazy + def isMemberPredicate(self): + return self.conceptManager['ismember'] + + +class JobPositionsOverview(QualificationBaseView, ConceptView): + + macroName = 'jobpositions' + + @Lazy + def positions(self): + result = [] + p = getPersonForUser(self.context, self.request) + if p is not None: + for parent in p.getParents([self.isMemberPredicate]): + if parent.conceptType == self.institutionType: + for child in parent.getChildren([self.defaultPredicate]): + if child.conceptType == self.jobPositionType: + result.append(child) + return result + + +class IPSkillsForm(QualificationBaseView, ConceptView): + """ Form for entering interpersonal skills required for a certain position. + """ + + macroName = 'ipskillsform' + diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index e4f02c9..06a3a27 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -23,6 +23,22 @@ factory="loops.knowledge.qualification.browser.Qualifications" permission="zope.View" /> + + + + + + + + +

Jobs / Positions

+ +
Deadline:
+ + + + +
+ + + + \ No newline at end of file diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index a98c638d83fc71ebaa3c0457ccd896eef5eb0515..bcc801f4c6ad0c2e886106ca284dc2ea687fddb0 100644 GIT binary patch delta 9419 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\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -298,7 +298,16 @@ msgstr "Rang Team" msgid "Team Size" msgstr "Anzahl der vom Team ausgefüllten Fragebögen" -# competence (qualification) +# compentence and qualification management + +msgid "Jobs / Positions" +msgstr "Stellen" + +msgid "Create Job..." +msgstr "Stelle anlegen..." + +msgid "Create a new job / position" +msgstr "Eine neue Stelle anlegen..." msgid "Validity Period (Months)" msgstr "Gültigkeitszeitraum (Monate)" diff --git a/organize/README.txt b/organize/README.txt index c37d7f3..adfe499 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -427,7 +427,7 @@ Send Email to Members >>> form.subject u"loops Notification from '$site'" >>> form.mailBody - u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.117\n\n' + u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.127\n\n' Show Presence of Other Users diff --git a/system/sync/README.txt b/system/sync/README.txt index 143c6a9..0cfb89d 100644 --- a/system/sync/README.txt +++ b/system/sync/README.txt @@ -18,7 +18,7 @@ Let's set up a loops site with basic and example concepts and resources. >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> len(concepts), len(resources), len(views) - (35, 3, 1) + (38, 3, 1) >>> from cybertools.tracking.btree import TrackingStorage >>> from loops.system.job import JobRecord diff --git a/xmlrpc/README.txt b/xmlrpc/README.txt index de55ea2..b12dd76 100755 --- a/xmlrpc/README.txt +++ b/xmlrpc/README.txt @@ -35,7 +35,7 @@ ZCML setup): Let's look what setup has provided us with: >>> len(concepts) - 24 + 27 Now let's add a few more concepts: @@ -72,7 +72,8 @@ note that the 'hasType' predicate is not shown as it should not be applied in an explicit assignment. >>> sorted(t['name'] for t in xrf.getConceptTypes()) - [u'competence', u'customer', u'domain', u'file', u'note', u'person', + [u'competence', u'customer', u'domain', u'file', u'ipskill', + u'ipskillsrequired', u'jobposition', u'note', u'person', u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> sorted(t['name'] for t in xrf.getPredicates()) [u'depends', u'issubtype', u'knows', u'ownedby', u'provides', u'requires', @@ -95,7 +96,8 @@ All methods that retrieve one object also returns its children and parents: >>> ch[0]['name'] u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) - [u'competence', u'customer', u'domain', u'file', u'note', u'person', + [u'competence', u'customer', u'domain', u'file', u'ipskill', + u'ipskillsrequired', u'jobposition', u'note', u'person', u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> pa = defaultPred['parents'] @@ -114,7 +116,8 @@ We can also retrieve children and parents explicitely: >>> ch[0]['name'] u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) - [u'competence', u'customer', u'domain', u'file', u'note', u'person', + [u'competence', u'customer', u'domain', u'file', u'ipskill', + u'ipskillsrequired', u'jobposition', u'note', u'person', u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> pa = xrf.getParents('5') @@ -174,14 +177,14 @@ Updating the concept map >>> topicId = xrf.getObjectByName('topic')['id'] >>> xrf.createConcept(topicId, u'zope2', u'Zope 2') - {'description': u'', 'title': u'Zope 2', 'type': '38', 'id': '76', + {'description': u'', 'title': u'Zope 2', 'type': '44', 'id': '86', 'name': u'zope2'} The name of the concept is checked by a name chooser; if the corresponding parameter is empty, the name will be generated from the title. >>> xrf.createConcept(topicId, u'', u'Python') - {'description': u'', 'title': u'Python', 'type': '38', 'id': '78', + {'description': u'', 'title': u'Python', 'type': '44', 'id': '88', 'name': u'python'} If we try to deassign a ``hasType`` relation nothing will happen; a From 93dcb301f9f26d8bcf8cffe17ff9e2cbd0d36e78 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 8 Aug 2014 08:33:35 +0200 Subject: [PATCH 048/269] move new knowledge.qualification stuff to cyberapps --- expert/README.txt | 6 +- expert/search.txt | 16 ++--- external/README.txt | 6 +- knowledge/data/knowledge_de.dmp | 9 --- knowledge/qualification/browser.py | 58 ------------------- knowledge/qualification/configure.zcml | 16 ----- .../qualification/qualification_macros.pt | 17 ------ organize/README.txt | 2 +- system/sync/README.txt | 2 +- xmlrpc/README.txt | 15 ++--- 10 files changed, 22 insertions(+), 125 deletions(-) delete mode 100644 knowledge/qualification/qualification_macros.pt diff --git a/expert/README.txt b/expert/README.txt index 7ac8ed8..35fe082 100644 --- a/expert/README.txt +++ b/expert/README.txt @@ -27,7 +27,7 @@ configuration): >>> concepts, resources, views = t.setup() >>> len(concepts) + len(resources) - 41 + 38 >>> loopsRoot = site['loops'] @@ -47,11 +47,11 @@ Type- and text-based queries >>> from loops.expert import query >>> qu = query.Title('ty*') >>> list(qu.apply()) - [0, 2, 79] + [0, 2, 70] >>> qu = query.Type('loops:*') >>> len(list(qu.apply())) - 41 + 38 >>> qu = query.Type('loops:concept:predicate') >>> len(list(qu.apply())) diff --git a/expert/search.txt b/expert/search.txt index 8905fc6..8b56b00 100755 --- a/expert/search.txt +++ b/expert/search.txt @@ -66,13 +66,13 @@ zcml in real life: >>> t = searchView.typesForSearch() >>> len(t) - 19 + 16 >>> t.getTermByToken('loops:resource:*').title 'Any Resource' >>> t = searchView.conceptTypesForSearch() >>> len(t) - 16 + 13 >>> t.getTermByToken('loops:concept:*').title 'Any Concept' @@ -91,7 +91,7 @@ a controller attribute for the search view. >>> searchView.submitReplacing('1.results', '1.search.form', pageView) 'submitReplacing("1.results", "1.search.form", - "http://127.0.0.1/loops/views/page/.target110/@@searchresults.html");...' + "http://127.0.0.1/loops/views/page/.target.../@@searchresults.html");...' Basic (text/title) search ------------------------- @@ -177,7 +177,7 @@ of the concepts' titles: >>> request = TestRequest(form=form) >>> view = Search(page, request) >>> view.listConcepts() - u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '115'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '117'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '119'}]}" + u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '...'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '...'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '...'}]}" Preset Concept Types on Search Forms ------------------------------------ @@ -219,13 +219,13 @@ and thus include the customer type in the preset search types. >>> searchView.conceptsForType('loops:concept:customer') [{'token': 'none', 'title': u'not selected'}, - {'token': '88', 'title': u'Customer 1'}, - {'token': '90', 'title': u'Customer 2'}, - {'token': '92', 'title': u'Customer 3'}] + {'token': '...', 'title': u'Customer 1'}, + {'token': '...', 'title': u'Customer 2'}, + {'token': '...', 'title': u'Customer 3'}] Let's use this new search option for querying: - >>> form = {'search.4.text_selected': u'84'} + >>> form = {'search.4.text_selected': u'75'} >>> resultsView = SearchResults(page, TestRequest(form=form)) >>> results = list(resultsView.results) >>> results[0].title diff --git a/external/README.txt b/external/README.txt index c98ad79..017ce46 100644 --- a/external/README.txt +++ b/external/README.txt @@ -17,7 +17,7 @@ Let's set up a loops site with basic and example concepts and resources. >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> len(concepts), len(resources), len(views) - (38, 3, 1) + (35, 3, 1) Importing loops Objects @@ -44,7 +44,7 @@ Creating the corresponding objects >>> loader = Loader(loopsRoot) >>> loader.load(elements) >>> len(concepts), len(resources), len(views) - (39, 3, 1) + (36, 3, 1) >>> from loops.common import adapted >>> adMyquery = adapted(concepts['myquery']) @@ -118,7 +118,7 @@ Extracting elements >>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export')) >>> elements = list(extractor.extract()) >>> len(elements) - 74 + 68 Writing object information to the external storage -------------------------------------------------- diff --git a/knowledge/data/knowledge_de.dmp b/knowledge/data/knowledge_de.dmp index 751cf9f..64fdaf4 100644 --- a/knowledge/data/knowledge_de.dmp +++ b/knowledge/data/knowledge_de.dmp @@ -1,12 +1,6 @@ type(u'competence', u'Qualifikation', viewName=u'', typeInterface=u'loops.knowledge.qualification.interfaces.ICompetence', options=u'action.portlet:create_subtype,edit_concept') -type(u'ipskill', u'Kompetenz', viewName=u'', - options=u'action.portlet:edit_concept') -type(u'ipskillsrequired', u'Soll-Profil', viewName=u'', - options=u'action.portlet:edit_concept') -type(u'jobposition', u'Stelle', viewName=u'', - options=u'action.portlet:edit_concept') type(u'person', u'Person', viewName=u'', typeInterface=u'loops.knowledge.interfaces.IPerson', options=u'action.portlet:createQualification,editPerson') @@ -41,9 +35,6 @@ concept(u'qualification_overview', u'Qualification Overview', u'report', # structure child(u'general', u'competence', u'standard') child(u'general', u'depends', u'standard') -child(u'general', u'ipskill', u'standard') -child(u'general', u'ipskillsrequired', u'standard') -child(u'general', u'jobposition', u'standard') child(u'general', u'knows', u'standard') child(u'general', u'person', u'standard') child(u'general', u'provides', u'standard') diff --git a/knowledge/qualification/browser.py b/knowledge/qualification/browser.py index 054e143..9d5b619 100644 --- a/knowledge/qualification/browser.py +++ b/knowledge/qualification/browser.py @@ -25,71 +25,13 @@ from zope import interface, component from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy -from cybertools.browser.action import actions -from loops.browser.action import DialogAction from loops.browser.concept import ConceptView from loops.expert.browser.report import ResultsConceptView from loops.organize.party import getPersonForUser from loops.util import _ -template = ViewPageTemplateFile('qualification_macros.pt') - - -actions.register('createJobPosition', 'portlet', DialogAction, - title=_(u'Create Job...'), - description=_(u'Create a new job / position.'), - viewName='create_concept.html', - dialogName='createPosition', - typeToken='.loops/concepts/jobposition', - fixedType=True, - innerForm='inner_concept_form.html', - permission='loops.AssignAsParent', -) - class Qualifications(ResultsConceptView): reportName = 'qualification_overview' - -class QualificationBaseView(object): - - template = template - templateName = 'knowledge.qualification' - - @Lazy - def institutionType(self): - return self.conceptManager['institution'] - - @Lazy - def jobPositionType(self): - return self.conceptManager['jobposition'] - - @Lazy - def isMemberPredicate(self): - return self.conceptManager['ismember'] - - -class JobPositionsOverview(QualificationBaseView, ConceptView): - - macroName = 'jobpositions' - - @Lazy - def positions(self): - result = [] - p = getPersonForUser(self.context, self.request) - if p is not None: - for parent in p.getParents([self.isMemberPredicate]): - if parent.conceptType == self.institutionType: - for child in parent.getChildren([self.defaultPredicate]): - if child.conceptType == self.jobPositionType: - result.append(child) - return result - - -class IPSkillsForm(QualificationBaseView, ConceptView): - """ Form for entering interpersonal skills required for a certain position. - """ - - macroName = 'ipskillsform' - diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index 06a3a27..e4f02c9 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -23,22 +23,6 @@ factory="loops.knowledge.qualification.browser.Qualifications" permission="zope.View" /> - - - - - - - - -

Jobs / Positions

- - - - - - -
-
- - - \ No newline at end of file diff --git a/organize/README.txt b/organize/README.txt index adfe499..66952ce 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -427,7 +427,7 @@ Send Email to Members >>> form.subject u"loops Notification from '$site'" >>> form.mailBody - u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.127\n\n' + u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.118\n\n' Show Presence of Other Users diff --git a/system/sync/README.txt b/system/sync/README.txt index 0cfb89d..143c6a9 100644 --- a/system/sync/README.txt +++ b/system/sync/README.txt @@ -18,7 +18,7 @@ Let's set up a loops site with basic and example concepts and resources. >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> len(concepts), len(resources), len(views) - (38, 3, 1) + (35, 3, 1) >>> from cybertools.tracking.btree import TrackingStorage >>> from loops.system.job import JobRecord diff --git a/xmlrpc/README.txt b/xmlrpc/README.txt index b12dd76..b237258 100755 --- a/xmlrpc/README.txt +++ b/xmlrpc/README.txt @@ -35,7 +35,7 @@ ZCML setup): Let's look what setup has provided us with: >>> len(concepts) - 27 + 24 Now let's add a few more concepts: @@ -72,8 +72,7 @@ note that the 'hasType' predicate is not shown as it should not be applied in an explicit assignment. >>> sorted(t['name'] for t in xrf.getConceptTypes()) - [u'competence', u'customer', u'domain', u'file', u'ipskill', - u'ipskillsrequired', u'jobposition', u'note', u'person', + [u'competence', u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> sorted(t['name'] for t in xrf.getPredicates()) [u'depends', u'issubtype', u'knows', u'ownedby', u'provides', u'requires', @@ -96,8 +95,7 @@ All methods that retrieve one object also returns its children and parents: >>> ch[0]['name'] u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) - [u'competence', u'customer', u'domain', u'file', u'ipskill', - u'ipskillsrequired', u'jobposition', u'note', u'person', + [u'competence', u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> pa = defaultPred['parents'] @@ -116,8 +114,7 @@ We can also retrieve children and parents explicitely: >>> ch[0]['name'] u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) - [u'competence', u'customer', u'domain', u'file', u'ipskill', - u'ipskillsrequired', u'jobposition', u'note', u'person', + [u'competence', u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'report', u'task', u'textdocument', u'topic', u'type'] >>> pa = xrf.getParents('5') @@ -177,14 +174,14 @@ Updating the concept map >>> topicId = xrf.getObjectByName('topic')['id'] >>> xrf.createConcept(topicId, u'zope2', u'Zope 2') - {'description': u'', 'title': u'Zope 2', 'type': '44', 'id': '86', + {'description': u'', 'title': u'Zope 2', 'type': '38', 'id': '77', 'name': u'zope2'} The name of the concept is checked by a name chooser; if the corresponding parameter is empty, the name will be generated from the title. >>> xrf.createConcept(topicId, u'', u'Python') - {'description': u'', 'title': u'Python', 'type': '44', 'id': '88', + {'description': u'', 'title': u'Python', 'type': '38', 'id': '79', 'name': u'python'} If we try to deassign a ``hasType`` relation nothing will happen; a From e5a2cfafa7c842363e04b93143e69183ebdda4a6 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 27 Aug 2014 10:49:33 +0200 Subject: [PATCH 049/269] remove messages moved to cyberapps.knowledge --- locales/de/LC_MESSAGES/loops.po | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 09a8588..404556d 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"Project-Id-Version: 0.13.0\n" +"Project-Id-Version: 0.13.1\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2014-07-30 12:00 CET\n" +"PO-Revision-Date: 2014-08-08 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -300,15 +300,6 @@ msgstr "Anzahl der vom Team ausgefüllten Fragebögen" # compentence and qualification management -msgid "Jobs / Positions" -msgstr "Stellen" - -msgid "Create Job..." -msgstr "Stelle anlegen..." - -msgid "Create a new job / position" -msgstr "Eine neue Stelle anlegen..." - msgid "Validity Period (Months)" msgstr "Gültigkeitszeitraum (Monate)" From 7e9a68bde1bf5f6f59b778463c9f29cd1c4fedb4 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 27 Aug 2014 10:53:04 +0200 Subject: [PATCH 050/269] allow specification of columns for data table in type object --- browser/form.py | 1 + table.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/browser/form.py b/browser/form.py index 04e3ade..e2c7a37 100755 --- a/browser/form.py +++ b/browser/form.py @@ -438,6 +438,7 @@ class CreateConceptForm(CreateObjectForm): return c ad = ti(c) ad.__is_dummy__ = True + ad.__type__ = adapted(self.typeConcept) return ad @Lazy diff --git a/table.py b/table.py index 0ccca60..d381f34 100644 --- a/table.py +++ b/table.py @@ -73,7 +73,10 @@ class DataTable(AdapterBase): _adapterAttributes = AdapterBase._adapterAttributes + ('columns', 'data') def getColumns(self): - return getattr(self.context, '_columns', ['key', 'value']) + cols = getattr(self.context, '_columns', None) + if not cols: + cols = getattr(baseObject(self.type), '_columns', None) + return cols or ['key', 'value'] def setColumns(self, value): self.context._columns = value columns = property(getColumns, setColumns) From 71cdde4308ba3448ea94dbf1a4b734cf85e05203 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 9 Sep 2014 17:32:54 +0200 Subject: [PATCH 051/269] avoid erroneous use of target view --- browser/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/node.py b/browser/node.py index a49a350..335deb6 100644 --- a/browser/node.py +++ b/browser/node.py @@ -440,7 +440,7 @@ class NodeView(BaseView): def targetView(self, name='index.html', methodName='show'): if name == 'index.html': # only when called for default view tv = self.viewAnnotations.get('targetView') - if tv is not None: + if tv is not None and callable(tv): return tv() if '?' in name: name, params = name.split('?', 1) From 7e975dadfb58c17d792484a8ab8a57c3d8527f08 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 27 Oct 2014 14:44:30 +0100 Subject: [PATCH 052/269] give portlet background color in order to hide too wide content regions (tables) --- browser/skin/lobo/lobo.css | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/skin/lobo/lobo.css b/browser/skin/lobo/lobo.css index 30fdd1a..1475732 100644 --- a/browser/skin/lobo/lobo.css +++ b/browser/skin/lobo/lobo.css @@ -20,6 +20,7 @@ body { #portlets { margin-top: 1em; + background-color: #ffffff; } ul.view-modes { From 5c0d46193ff9a8055de83265c8cc567ce1bed4b4 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 27 Oct 2014 14:45:01 +0100 Subject: [PATCH 053/269] provide separate function for generating object names from title --- common.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/common.py b/common.py index 6c90712..5a9b165 100644 --- a/common.py +++ b/common.py @@ -224,17 +224,19 @@ class NameChooser(BaseNameChooser): return name def generateNameFromTitle(self, obj): - title = obj.title - if len(title) > 15: - words = title.split() - if len(words) > 1: - title = '_'.join((words[0], words[-1])) - return self.normalizeName(title) + return generateNameFromTitle(obj.title) def normalizeName(self, baseName): return normalizeName(baseName) +def generateNameFromTitle(title): + if len(title) > 15: + words = title.split() + if len(words) > 1: + title = '_'.join((words[0], words[-1])) + return normalizeName(title) + def normalizeName(baseName): specialCharacters = { '\xc4': 'Ae', '\xe4': 'ae', '\xd6': 'Oe', '\xf6': 'oe', From 53d9a5b3a4c454b67e496706b45e46817aaa0488 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 2 Nov 2014 14:10:02 +0100 Subject: [PATCH 054/269] store salutation and academic title upon registration; do not show these fields in self-service registration --- organize/browser/member.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/organize/browser/member.py b/organize/browser/member.py index f80044c..8abb2d0 100644 --- a/organize/browser/member.py +++ b/organize/browser/member.py @@ -191,7 +191,9 @@ class MemberRegistration(BaseMemberRegistration, CreateForm): result = regMan.register(login, pw, form.get('lastName'), form.get('firstName'), email=form.get('email'), - phoneNumbers=[x for x in phoneNumbers if x]) + phoneNumbers=[x for x in phoneNumbers if x], + salutation=form.get('salutation'), + academicTitle=form.get('academicTitle')) if isinstance(result, dict): fi = formState.fieldInstances[result['fieldName']] fi.setError(result['error'], self.formErrors) @@ -214,6 +216,8 @@ class SecureMemberRegistration(BaseMemberRegistration, CreateForm): @Lazy def schema(self): schema = super(SecureMemberRegistration, self).schema + schema.fields.remove('salutation') + schema.fields.remove('academicTitle') schema.fields.remove('birthDate') schema.fields.remove('password') schema.fields.remove('passwordConfirm') From 40d368602ddd76036aa3c465c34ce1b44eae7adb Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 5 Nov 2014 08:37:18 +0100 Subject: [PATCH 055/269] fix state check in search --- expert/browser/search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/expert/browser/search.py b/expert/browser/search.py index b1495ba..575a97f 100644 --- a/expert/browser/search.py +++ b/expert/browser/search.py @@ -272,8 +272,8 @@ class Search(ConceptView): for state in states: if stf.state == state: break - else: - return False + else: + return False return True From bd85bfbcdc8d1da390c0c2b52b005f9e67c5ea72 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 17 Nov 2014 09:08:45 +0100 Subject: [PATCH 056/269] allow re-open (retract to draft state) of active task --- organize/stateful/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/organize/stateful/task.py b/organize/stateful/task.py index dea88d4..0c2bb48 100644 --- a/organize/stateful/task.py +++ b/organize/stateful/task.py @@ -54,7 +54,7 @@ def taskStates(): return StatesDefinition('task_states', State('draft', 'draft', ('release', 'cancel',), color='blue'), - State('active', 'active', ('finish', 'cancel',), + State('active', 'active', ('finish', 'reopen', 'cancel',), color='yellow'), State('finished', 'finished', ('reopen', 'archive',), color='green'), From b1df34621e23ee77c5c971bd38c716fd319d0950 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 24 Nov 2014 10:45:04 +0100 Subject: [PATCH 057/269] fix: add missing import --- expert/field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expert/field.py b/expert/field.py index 1695b61..e54d998 100644 --- a/expert/field.py +++ b/expert/field.py @@ -28,7 +28,7 @@ from zope.schema.interfaces import IVocabularyFactory, IContextSourceBinder from cybertools.composer.report.field import Field as BaseField from cybertools.composer.report.result import ResultSet from cybertools.stateful.interfaces import IStateful, IStatesDefinition -from cybertools.util.date import timeStamp2Date +from cybertools.util.date import timeStamp2Date, timeStamp2ISO from cybertools.util.format import formatDate from loops.common import baseObject from loops.expert.report import ReportInstance From f3d595a974712a617fb63ca8c6f3ff97129fcb31 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 28 Nov 2014 16:28:28 +0100 Subject: [PATCH 058/269] provide 'noprint' CSS class that exludes elements from print output --- browser/skin/lobo/lobo.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/browser/skin/lobo/lobo.css b/browser/skin/lobo/lobo.css index 1475732..3281a0d 100644 --- a/browser/skin/lobo/lobo.css +++ b/browser/skin/lobo/lobo.css @@ -109,6 +109,14 @@ thead th { background: none; } +/* printing */ + +@media print { + .noprint { + display: none; + } +} + /* class-specific */ .breadcrumbs td { From 0ca62cbe914af1f7dd32bf47abee0dac2542f6ea Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 30 Nov 2014 13:24:32 +0100 Subject: [PATCH 059/269] tolerate missing (probably deleted) task when editing work item --- organize/work/browser.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/organize/work/browser.py b/organize/work/browser.py index 6577913..c2ca89e 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -43,6 +43,7 @@ from loops.browser.concept import ConceptView from loops.browser.form import ObjectForm, EditObject from loops.browser.node import NodeView from loops.common import adapted +from loops.interfaces import IConcept from loops.organize.interfaces import IPerson from loops.organize.party import getPersonForUser from loops.organize.stateful.browser import StateAction @@ -410,10 +411,11 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): task = self.task if task is None: task = self.target - options = IOptions(adapted(task.conceptType)) - typeNames = options.workitem_types - if typeNames: - return [workItemTypes[name] for name in typeNames] + if IConcept.providedBy(task): + options = IOptions(adapted(task.conceptType)) + typeNames = options.workitem_types + if typeNames: + return [workItemTypes[name] for name in typeNames] return workItemTypes @Lazy @@ -483,6 +485,8 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): task = self.task if task is None: task = self.target + if not IConcept.providedBy(task): + return [] options = IOptions(adapted(task.conceptType)) return options.hidden_workitem_actions or [] From 075badc55afce1e00f67b40717ecf99903420db3 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 15 Jan 2015 10:11:57 +0100 Subject: [PATCH 060/269] make delete permission configurable via global option --- expert/browser/search.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/expert/browser/search.py b/expert/browser/search.py index 575a97f..c264073 100644 --- a/expert/browser/search.py +++ b/expert/browser/search.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -90,7 +90,8 @@ class Search(ConceptView): @Lazy def showActions(self): - return checkPermission('loops.ManageSite', self.context) + perm = (self.globalOptions('delete_permission') or ['loops.ManageSite'])[0] + return checkPermission(perm, self.context) #return canWriteObject(self.context) @property From a13f2218db51d82215588c16a4cd0705515f304b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 12 Feb 2015 12:09:54 +0100 Subject: [PATCH 061/269] provide type option for using resource title for download filename --- browser/resource.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/browser/resource.py b/browser/resource.py index 2b7a7ec..b502c23 100644 --- a/browser/resource.py +++ b/browser/resource.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -20,6 +20,7 @@ View class for resource objects. """ +import os.path import urllib from zope.cachedescriptors.property import Lazy from zope import component @@ -47,7 +48,7 @@ from loops.browser.common import EditForm, BaseView from loops.browser.concept import BaseRelationView, ConceptRelationView from loops.browser.concept import ConceptConfigureView from loops.browser.node import NodeView, node_macros -from loops.common import adapted, NameChooser, normalizeName +from loops.common import adapted, baseObject, NameChooser, normalizeName from loops.interfaces import IBaseResource, IDocument, ITextDocument from loops.interfaces import IMediaAsset as legacy_IMediaAsset from loops.interfaces import ITypeConcept @@ -211,6 +212,16 @@ class ResourceView(BaseView): data = context.data if useAttachment: filename = adapted(self.context).localFilename or getName(self.context) + if self.typeOptions('use_title_for_download_filename'): + base, ext = os.path.splitext(filename) + filename = context.title + vr = IVersionable(baseObject(context)) + if len(vr.versions) > 0: + filename = vr.generateName(filename, ext, vr.versionId) + else: + if not filename.endswith(ext): + filename += ext + filename = filename.encode('UTF-8') if self.typeOptions('no_normalize_download_filename'): filename = '"%s"' % filename else: From 1cec74642324d451ca577b670c80e7f8bbefb969 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 2 Mar 2015 13:36:22 +0100 Subject: [PATCH 062/269] two type option for restricting qualifier 'assign' settings --- browser/form.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/browser/form.py b/browser/form.py index e2c7a37..4482df4 100755 --- a/browser/form.py +++ b/browser/form.py @@ -212,15 +212,37 @@ class ObjectForm(NodeView): def typeManager(self): return ITypeManager(self.target) + @Lazy + def targetType(self): + return self.target.getType() + @Lazy def presetTypesForAssignment(self): - types = list(self.typeManager.listTypes(include=('assign',))) + types = [] + tn = getName(self.targetType) + for t in self.typeManager.listTypes(include=('assign',)): + # check if type is appropriate for the object to be created + opt = IOptions(adapted(t.context))('qualifier_assign_to') + #print '***', t.context.__name__, opt, tn + if not opt or tn in opt: + types.append(t) assigned = [r.context.conceptType for r in self.assignments] types = [t for t in types if t.typeProvider not in assigned] return [dict(title=t.title, token=t.tokenForSearch) for t in types] def conceptsForType(self, token): result = ConceptQuery(self).query(type=token) + # check typeOption: include only matching instances + include = [] + type = self.conceptManager[token.split(':')[-1]] + #print '###', token, repr(type) + opt = IOptions(adapted(type))('qualifier_assign_check_parents') + if opt: + for p in self.target.getAllParents([self.defaultPredicate]): + for c in p.object.getChildren([self.defaultPredicate]): + include.append(c) + if include: + result = [c for c in result if c in include] fv = FilterView(self.context, self.request) result = fv.apply(result) result.sort(key=lambda x: x.title) @@ -325,6 +347,10 @@ class CreateObjectForm(ObjectForm): if typeToken: return self.loopsRoot.loopsTraverse(typeToken) + @Lazy + def targetType(self): + return self.typeConcept + @Lazy def adapted(self): ad = self.typeInterface(Resource()) From bc0949842ea2dd23724dcb72917751a92abd6090 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 7 Mar 2015 09:40:17 +0100 Subject: [PATCH 063/269] provide utility view for fixing person roles (set to 'loops.Person') --- organize/browser/configure.zcml | 8 ++++++++ organize/browser/member.py | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/organize/browser/configure.zcml b/organize/browser/configure.zcml index 3548bed..3aa0336 100644 --- a/organize/browser/configure.zcml +++ b/organize/browser/configure.zcml @@ -152,4 +152,12 @@ permission="zope.ManageServices" menu="zmi_views" title="Prefix" /> + + + + diff --git a/organize/browser/member.py b/organize/browser/member.py index 8abb2d0..7aabe80 100644 --- a/organize/browser/member.py +++ b/organize/browser/member.py @@ -500,3 +500,15 @@ class PasswordReset(PasswordChange): mailhost = component.getUtility(IMailDelivery, 'Mail') mailhost.send(sender, recipients, msg.as_string()) + +class FixPersonRoles(object): + + def __call__(self): + concepts = self.context['concepts'] + for p in concepts['person'].getChildren([concepts['hasType']]): + person = adapted(p) + userId = person.userId + print '***', userId + person.userId = userId + return 'blubb' + From 1716990d26ffde18f38739b7a24ed7fe2663bfde Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 10 Mar 2015 09:12:22 +0100 Subject: [PATCH 064/269] avoid error because of missing field on answer options --- knowledge/survey/view_macros.pt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 1a32b32..35a26f5 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -83,7 +83,7 @@ + tal:content="opt/label|string:" /> From e16f3e5ccf7cffac26d2fe54fc28e5fbae7ecb9a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 13 Mar 2015 14:27:30 +0100 Subject: [PATCH 065/269] provide CSV download view for reports; use for work and qualification listings --- expert/browser/export.py | 77 ++++++++++++++++++++++++++ expert/field.py | 21 ++++++- knowledge/qualification/browser.py | 8 ++- knowledge/qualification/configure.zcml | 6 ++ organize/work/README.txt | 12 ++++ organize/work/configure.zcml | 6 ++ organize/work/report.py | 6 ++ 7 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 expert/browser/export.py diff --git a/expert/browser/export.py b/expert/browser/export.py new file mode 100644 index 0000000..8c06e21 --- /dev/null +++ b/expert/browser/export.py @@ -0,0 +1,77 @@ +# +# Copyright (c) 2015 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 +# + +""" +View classes for export of report results. +""" + +import csv +from cStringIO import StringIO +from zope.i18n import translate + +from loops.expert.browser.report import ReportConceptView +from loops.interfaces import ILoopsObject +from loops.util import _ + + +class ReportConceptCSVExport(ReportConceptView): + + isToplevel = True + reportMode = 'export' + + delimiter = ';' + encoding = 'UTF-8' + + def getFileName(self): + return 'output' + + def getColumnTitle(self, field): + lang = self.languageInfo.language + return translate(_(field.title), target_language=lang) + + def __call__(self): + fields = self.displayedColumns + fieldNames = [f.name for f in fields] + output = StringIO() + writer = csv.DictWriter(output, fieldNames, delimiter=self.delimiter) + output.write(self.delimiter.join( + [self.getColumnTitle(f) for f in fields]) + '\n') + results = self.reportInstance.getResults() + for row in results: + data = {} + for f in fields: + value = f.getValue(row) + if ILoopsObject.providedBy(value): + value = value.title + if isinstance(value, unicode): + value = value.encode(self.encoding) + data[f.name] = value + writer.writerow(data) + text = output.getvalue() + self.setDownloadHeader(text) + return text + + def setDownloadHeader(self, text): + response = self.request.response + response.setHeader('Content-Disposition', + 'attachment; filename=%s.csv' % + self.getFileName()) + response.setHeader('Cache-Control', '') + response.setHeader('Pragma', '') + response.setHeader('Content-Type', 'text/csv') + response.setHeader('Content-Length', len(text)) diff --git a/expert/field.py b/expert/field.py index e54d998..2263b84 100644 --- a/expert/field.py +++ b/expert/field.py @@ -22,6 +22,7 @@ Field definitions for reports. from zope.app.form.browser.interfaces import ITerms from zope import component +from zope.i18n import translate from zope.i18n.locales import locales from zope.schema.interfaces import IVocabularyFactory, IContextSourceBinder @@ -92,6 +93,11 @@ class DateField(Field): format = ('date', 'short') cssClass = 'center' + def getValue(self, row): + if getattr(row.parent.context.view, 'reportMode', None) == 'export': + return self.getDisplayValue(row) + super(DateField, self).getValue(row) + def getDisplayValue(self, row): value = self.getRawValue(row) if not value: @@ -132,6 +138,15 @@ class WorkItemStateField(Field): statesDefinition = 'workItemStates' renderer = 'workitem_state' + def getValue(self, row): + view = row.parent.context.view + if getattr(view, 'reportMode', None) == 'export': + stateObject = row.context.getStateObject() + lang = view.languageInfo.language + return translate(util._(stateObject.title), target_language=lang) + return super(WorkItemStateField, self).getValue(row) + + def getDisplayValue(self, row): if row.context is None: return None @@ -241,14 +256,18 @@ class TrackDateField(Field): cssClass = 'right' def getValue(self, row): + reportMode = getattr(row.parent.context.view, 'reportMode', None) + if reportMode == 'export': + return self.getDisplayValue(row) value = self.getRawValue(row) if not value: return None return timeStamp2Date(value) def getDisplayValue(self, row): - value = self.getValue(row) + value = self.getRawValue(row) if value: + value = timeStamp2Date(value) view = row.parent.context.view return formatDate(value, self.part, self.format, view.languageInfo.language) diff --git a/knowledge/qualification/browser.py b/knowledge/qualification/browser.py index 9d5b619..186642f 100644 --- a/knowledge/qualification/browser.py +++ b/knowledge/qualification/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -26,6 +26,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from loops.browser.concept import ConceptView +from loops.expert.browser.export import ReportConceptCSVExport from loops.expert.browser.report import ResultsConceptView from loops.organize.party import getPersonForUser from loops.util import _ @@ -35,3 +36,8 @@ class Qualifications(ResultsConceptView): reportName = 'qualification_overview' + +class QualificationsCSVExport(ReportConceptCSVExport): + + reportName = 'qualification_overview' + diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index e4f02c9..b6a1a5e 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -23,6 +23,12 @@ factory="loops.knowledge.qualification.browser.Qualifications" permission="zope.View" /> + + >> results.totals.data {'effort': 900} +Export of work data +------------------- + + >>> from loops.organize.work.report import WorkStatementCSVExport + >>> reportView = WorkStatementCSVExport(task01, TestRequest()) + >>> reportView.nodeView = nodeView + + >>> output = reportView() + >>> print output + Day;Start;End;Task;Party;Title;Duration;Effort;State + 08/12/28;19:00;20:15;loops Development;john;;1.25;0.25;finished + Meeting Minutes =============== diff --git a/organize/work/configure.zcml b/organize/work/configure.zcml index 17dac6c..d4eab19 100644 --- a/organize/work/configure.zcml +++ b/organize/work/configure.zcml @@ -101,6 +101,12 @@ class="loops.organize.work.report.WorkStatementView" permission="zope.View" /> + + Date: Fri, 13 Mar 2015 15:14:29 +0100 Subject: [PATCH 066/269] CSV download improvements: button, file name, ... --- expert/browser/export.py | 7 ++++--- expert/browser/report.py | 6 ++++++ expert/browser/results.pt | 7 +++++++ knowledge/qualification/browser.py | 4 ++-- locales/de/LC_MESSAGES/loops.mo | Bin 26048 -> 25942 bytes locales/de/LC_MESSAGES/loops.po | 7 ++++++- organize/work/report.py | 4 ++-- 7 files changed, 27 insertions(+), 8 deletions(-) diff --git a/expert/browser/export.py b/expert/browser/export.py index 8c06e21..f27c105 100644 --- a/expert/browser/export.py +++ b/expert/browser/export.py @@ -24,12 +24,13 @@ import csv from cStringIO import StringIO from zope.i18n import translate -from loops.expert.browser.report import ReportConceptView +from loops.common import normalizeName +from loops.expert.browser.report import ResultsConceptView from loops.interfaces import ILoopsObject from loops.util import _ -class ReportConceptCSVExport(ReportConceptView): +class ResultsConceptCSVExport(ResultsConceptView): isToplevel = True reportMode = 'export' @@ -38,7 +39,7 @@ class ReportConceptCSVExport(ReportConceptView): encoding = 'UTF-8' def getFileName(self): - return 'output' + return normalizeName(self.context.title) def getColumnTitle(self, field): lang = self.languageInfo.language diff --git a/expert/browser/report.py b/expert/browser/report.py index acf6279..395d5f3 100644 --- a/expert/browser/report.py +++ b/expert/browser/report.py @@ -190,6 +190,12 @@ class ResultsConceptView(ConceptView): def getColumnRenderer(self, col): return self.result_macros[col.renderer] + @Lazy + def downloadLink(self, format='csv'): + opt = self.options('download_' + format) + if opt: + return opt[0] + class EmbeddedResultsConceptView(ResultsConceptView): diff --git a/expert/browser/results.pt b/expert/browser/results.pt index b9d9ed1..62a9085 100644 --- a/expert/browser/results.pt +++ b/expert/browser/results.pt @@ -37,6 +37,13 @@

+ +
+
+ diff --git a/knowledge/qualification/browser.py b/knowledge/qualification/browser.py index 186642f..5c0a061 100644 --- a/knowledge/qualification/browser.py +++ b/knowledge/qualification/browser.py @@ -26,7 +26,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from loops.browser.concept import ConceptView -from loops.expert.browser.export import ReportConceptCSVExport +from loops.expert.browser.export import ResultsConceptCSVExport from loops.expert.browser.report import ResultsConceptView from loops.organize.party import getPersonForUser from loops.util import _ @@ -37,7 +37,7 @@ class Qualifications(ResultsConceptView): reportName = 'qualification_overview' -class QualificationsCSVExport(ReportConceptCSVExport): +class QualificationsCSVExport(ResultsConceptCSVExport): reportName = 'qualification_overview' diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index bcc801f4c6ad0c2e886106ca284dc2ea687fddb0..df8afd747c7f0c1ec989f7ee6b9d21c84e029d75 100644 GIT binary patch delta 9337 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 P<;$~keao*5xDfhZ#s<;L delta 9413 zcmZA634Bgh{>SkfiA0D}EWuwYHMlw-!}e%e0~O zA55uMJJxB@PRCYVEX8zFrIya;``n*-`Okgzm-jj6o_qFtleV89_c?sR$8$N%Z-v9P z$H#Fh;48%)C#8_%1eI5*Mv!lXrLZrmgR!Uvr`!Gc=0?;&_hJz| zVEGSG^-o|)`ghJ-g_~C4XDmU52dJ3^R&$*07>cQwg=%mo7RG(3dIzlhsO3*$Ny^V* z7~Zh^4^ac~i(y6R-w7tcQaF)j9IAuH7>X^?AA8vS3{(Tp+x=YB%FISB@mf^JZ((VC zAJxtmsEOS~)q8-RDkO?lcbu{qkKvexdaysL;qj=YehJm^YSb3&L^XUA)!=!{U&Auw z@1tfOTEl(5Dr&3ipeCGBgZ0;lI#Hm82VyzQMRmLwHLxwH0UfgP^Qii_Py_x0RWH1z z20xmhShEPy_FyI`|N^bf+wT4b|ZfsFezgbL*Ey_#oo5mbX;pdP%1YVZ+iDTCwPnMR}T z$DHQx?LKVlNI-H6c$aK_7%)xrN#>!8l z>YvBrc*VSfmB>Ft9lmn4+<_;Xtx*H-fZFl_Qt$sz5^8V+YNX>VKNU5Q*{Bs*h&l`T zsG06U4Y&X`(DzUsoIusPh{Nz6Y6~+G-7OrACCJZ0j~-l3LKU{5-s}CSnVrF4yo7rF zZlmh^)ppCvpvq%Vd)*xMTnE%4?uVK{9%=$pQ4^en8u-H6tiSeZDFu399cm@^ntwwr z)hDQiPg(u~G8X46?1-Ut+(SGF`D#14s18@72KK6z@4^uBXYKyCby$B@yloZlqL$XD zt~=5IR7WAGnWUiJlD4SRJP`F;b0o&&T&#|Jkz?Ro!WazV09D0$s1Hi#s;%6Mjz z&>k+c8#_>2@Q&pVqGs@s z8A3u8pGUsF&Lj-O&8Snn54Ch>Py@Y-Y9OegyVnt@0X0Dlqy=gKsi=u`Lv2-myFUVT z21cWP|2Wf0Xm7V;DDFWubOiZB$oUwxHOEo)K0__-m#B{KpgQ~+_1qIw$9|36j)PJ8 zvX+lVZAk()(EFcELLKL!I-G+#O!HAo|1NSaoy(Ysp^e=?$tIwd^cd>b_}8eFDV$^- zqXtwK!!Z)|+SRl2M(9z6W+XJD)~KcGgWB6n)PTmKRv-^G<7pUyGf?$bqUvq2``b|U z-@-CjfO?Bg;sShZ_vbZX{WY?sP27&RqGqrM)zN-b!-r5yco^H@apcu^LXzDXwnRPG z7WG_L)Bt;-CX{XYNvL{@QCqMwne~q&v6X^I{1mmvKUu~5miKAu9?Bx9B`lAcVI-=- z7}Rr#sDU;_4Il+eVQa%k`;9H1j+#(^)Cx>Qbv)D6avJy4p4X6%YM;)>^Q4hX{ z+JZw?egswTH0rst<^}T`)PQcGR_u|L`?pYi4<8>As!$3wkQnstC29{Fq8jdqy5H0C z1I^*60py_fHAWq-Ij9d?zPZ_a+dPb+^zWP@p%2Ja)J*T7I{FWsXL`d<7Ubx1Fvw*xCL%ILJzl~4nyj;dD=b(Y$r+RJFo`m5qFE670&XcDS?sksKV zMO#n}?!`fP82QJEYU3_>7V41Yp`M>^`T3|V%tsySb*Og!(uVcdNDfkR_qu?;YHMh9$R^t4(^gCptdjtBk14h zLqdC!W4?%5qSdICdB^f+P~Y;Kr~wq}=yp&Jbr$NQUf(oSd%et2sQNQd16++-$=&GD zQhZ&E-kJ4R1D7b!%)T?fM~(Cc^u>p$0sVn$Ah3&T7-}HpQ3LObsy_&|9Yz!Dhc>FVxDB5DOvQLj@!)Lu_UHM9&vaR+LK2T)sd9CiOHhT;P(kHxyV9mimC z@=2(bXp5TpDAdF~xg^x_RMg0qU=S`x9llMd1`eV|d>qx$zc2)Eqh|gX)6lm&zw5C* z>MU(XE&Xv+hd-hwa35po-wEj9&ZI7CY12^y=#DxYgUwNPeHcebRg5Fw7dh9?T&#}gFkbKfV-nF6#P)LcyfbQPGf+#Fi(xng)!`!4j9)=D zv=%kcqc{jpqs~%7Z+9!}Vg&iFs1+N5YHza2_5Lp;p&7r1TI$_c3J;NAtb@x@1N#UyfKzt=oaMhZZ}w&VRpCbp%HVIPLl@G|9Y{QC zAdOK=-U0PJ=w|tzsE#tQBxYKElIcOsbRKFYUq;nii}AR-pS}MVDNqOBVnh5M)p3;! zx5N5ak9;H4Z?(~wgo`i_KgMp@gri>rm!MAle$>idMh)a9s-K^+Hs1G;(4N#B;NEDA zdZ0P#HA_dWP=8dtOw&cX`#HU`nZbJ{9gKs9{L{5R_LyoXwWpiFll5vUo)p&G7>y5G$5X{Z5oLro~t z%EzL%bQ)@)3((_BBAA7$JFsG?_A3ly{gsHLKqE^=jVv9tS3OW2W?T7WEK7ct<=3M+*oJz3x0N46t;jLd zfX<-$xr%!2enK7c=q%P>d*3L_-J3L2LlaRA&qh7C2sM!Ps3qNwn)yET!7}_%JG$x< zuX|Jcnx#CNbZOL{|4bAkUCr(v(Ei6$P>0Z^{l4r?@zuhylt*DC4kBJ6{T)$@(AANc zPPwi>o9jrQCn}SFgBU@2JHAM~KwKx^n9!BKy?l^oGl{LBivK3g=o_kJdD_>@|$bYRuNzWVPmQt$`v4iw5OvbeY-*RUl z`TjT)pI-Ax7a{&c!6-B8lLW^Z%~8 z>V?RXTyGJ(NH4Oy$}SLpCk7L`J|bS_ejnUP>?KwaUlXtUu!vo_Ihu^FjYMzqDHwxI zh?cs+^$pR9e0L(8SV(>@>i3+kuB6N3UBZvFu2}p8w_#hNowwBe4WLg{lnrFuI5CzH^sNi%05>)=`#4K-4oAIb`iS}vq=A_Lauj-H;INs1Im5zHnD@W z4$*(FMWlYU++r+5g1JYJ{%c#4#e9I75sibQLDXSe-d$CCWMw ztH^u%pQDO)lce(%k!|@+r1Ob1^8N6ALf2-y-}IU1qMu2-wfJ_C=w;AovUgo}c{s*n%3A|(J7fjKJxI(;2?Bv0GB8+rRViM^ISP^sa>9vGJ zFUuUqjojZvjCooCTd4mE6wW16i1?8B2ho_acEobhJ&8SpuGy3Y5`S^CPJhxzN%zDe z*pygJ`lj8lh~vl)#ky7|&iyyd{QFVif3JNMbfes#m`QpYQI>RXt2mK#57NI7BT4@o z+vERX9pXje9U_Rb|GloMASG&a*07NiMi)GYp6utFK4xga@ak{)1ivt5XjHAJv19T^ zjvqNDr{Iqoi+u{##!m7rF=l8UH@eu3yn+(7PKNp<=3i{lETCR|V#D~li3OEY{QdpA wkI%}^&MGLM7E&n0O-BvM$<7*)m6MQA5ZUgWPeD-oF@6E=t(=0Xoi9fG2Y3|lF#rGn diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 404556d..0b357ea 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.1\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2014-08-08 12:00 CET\n" +"PO-Revision-Date: 2015-03-13 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -89,6 +89,11 @@ msgstr "Thema ändern" msgid "Please correct the indicated errors." msgstr "Bitte berichtigen Sie die angezeigten Fehler." +# expert (reporting) + +msgid "Download Data" +msgstr "Download als Excel-Datei" + # blog msgid "Edit Blog Post..." diff --git a/organize/work/report.py b/organize/work/report.py index 0d7dd14..c3e90d1 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -32,7 +32,7 @@ from cybertools.organize.interfaces import IWorkItems from cybertools.util.date import timeStamp2Date, timeStamp2ISO from cybertools.util.jeep import Jeep from loops.common import adapted, baseObject -from loops.expert.browser.export import ReportConceptCSVExport +from loops.expert.browser.export import ResultsConceptCSVExport from loops.expert.browser.report import ReportConceptView from loops.expert.field import Field, TargetField, DateField, StateField, \ TextField, HtmlTextField, UrlField @@ -50,7 +50,7 @@ class WorkStatementView(ReportConceptView): reportName = 'work_statement' -class WorkStatementCSVExport(ReportConceptCSVExport): +class WorkStatementCSVExport(ResultsConceptCSVExport): reportName = 'work_statement' From 2c112703a3b20fc5ceee2ed73bd3c3cbbdf65d4c Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 14 Mar 2015 11:41:10 +0100 Subject: [PATCH 067/269] optionally allow time part for deadline --- organize/work/browser.py | 30 ++++++++++++++++++++++++++++-- organize/work/work_macros.pt | 13 +++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/organize/work/browser.py b/organize/work/browser.py index c2ca89e..9678a83 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -85,6 +85,14 @@ class WorkItemDetails(TrackDetails): def deadline(self): return self.formatTimeStamp(self.track.deadline, 'date') + @Lazy + def deadlineTime(self): + return self.formatTimeStamp(self.track.deadline, 'time') + + @Lazy + def deadlineWithTime(self): + return self.globalOptions('organize.work.deadline_with_time') + @Lazy def start(self): result = self.formatTimeStamp(self.track.start, 'time') @@ -429,6 +437,17 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): return time.strftime('%Y-%m-%d', time.localtime(ts)) return '' + @Lazy + def deadlineTime(self): + ts = self.track.deadline# or getTimeStamp() + if ts: + return time.strftime('T%H:%M', time.localtime(ts)) + return '' + + @Lazy + def deadlineWithTime(self): + return self.globalOptions('organize.work.deadline_with_time') + @Lazy def defaultTimeStamp(self): if self.workItemType.prefillDate: @@ -583,7 +602,14 @@ class CreateWorkItem(EditObject, BaseTrackView): setValue('party') if action == 'move': setValue('task') - result['deadline'] = parseDate(form.get('deadline')) + #result['deadline'] = parseDate(form.get('deadline')) + deadline = form.get('deadline') + if deadline: + deadlineTime = (form.get('deadline_time', ''). + strip().replace('T', '') or '00:00:00') + result['deadline'] = parseDateTime('T'.join((deadline, deadlineTime))) + else: + result['deadline'] = None startDate = form.get('start_date', '').strip() endDate = form.get('end_date', '').strip() or startDate startTime = form.get('start_time', '').strip().replace('T', '') or '00:00:00' diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 6ce4695..80aa11e 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -157,7 +157,12 @@
+ tal:attributes="value view/deadline" /> + +
@@ -243,7 +248,11 @@
- + From beff6ef2ddb863199bfac269d691ae16eb9b0745 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 16 Mar 2015 11:17:38 +0100 Subject: [PATCH 068/269] new fields: priority, activity --- organize/work/browser.py | 17 ++++++++++- organize/work/work_macros.pt | 55 +++++++++++++++++++++++++++--------- table.py | 13 +++++++++ 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/organize/work/browser.py b/organize/work/browser.py index 9678a83..ca8d058 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -524,7 +524,10 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): return [dict(name=util.getUidForObject(p), title=p.title) for p in persons] - taskTypes = ['task', 'event', 'agendaitem'] + @Lazy + def taskTypes(self): + return (self.globalOptions('organize.work.task_types') or + ['task', 'event', 'agendaitem']) @Lazy def followUpTask(self): @@ -544,6 +547,16 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): return [dict(name=util.getUidForObject(t), title=t.title) for t in tasks] + @Lazy + def priorities(self): + prio = self.conceptManager.get('organize.work.priorities') + return prio and adapted(prio).dataAsRecords or [] + + @Lazy + def activities(self): + act = self.conceptManager.get('organize.work.activities') + return act and adapted(act).dataAsRecords or [] + @Lazy def duration(self): if self.state == 'running': @@ -610,6 +623,8 @@ class CreateWorkItem(EditObject, BaseTrackView): result['deadline'] = parseDateTime('T'.join((deadline, deadlineTime))) else: result['deadline'] = None + result['priority'] = form.get('priority') + result['activity'] = form.get('activity') startDate = form.get('start_date', '').strip() endDate = form.get('end_date', '').strip() or startDate startTime = form.get('start_time', '').strip().replace('T', '') or '00:00:00' diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 80aa11e..abc9682 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -150,19 +150,48 @@ view.getUidForObject(view.followUpTask)" /> -
-
- -
- - -
+
+ +
+ + +
+
+
Deadline: + + +
Start - End:
+ + + + + + + +
+ +
+ + + +
diff --git a/table.py b/table.py index d381f34..d09c194 100644 --- a/table.py +++ b/table.py @@ -93,6 +93,19 @@ class DataTable(AdapterBase): self.context._data = OOBTree(data) data = property(getData, setData) + @property + def dataAsRecords(self): + result = [] + for k, v in sorted(self.data.items()): + item = {} + for idx, c in enumerate(self.columns): + if idx == 0: + item[c] = k + else: + item[c] = v[idx-1] + result.append(item) + return result + TypeInterfaceSourceList.typeInterfaces += (IDataTable,) From c4ce28e2396b86cda77ee4dc05eb3de078c493a8 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 16 Mar 2015 12:10:34 +0100 Subject: [PATCH 069/269] add utility property and function for accessing data table data as records/rows --- README.txt | 6 ++++++ table.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/README.txt b/README.txt index 6ec6fc2..bbffa03 100755 --- a/README.txt +++ b/README.txt @@ -913,6 +913,12 @@ relates ISO country codes with the full name of the country. >>> sorted(adapted(concepts['countries']).data.items()) [('at', ['Austria']), ('de', ['Germany'])] + >>> countries.dataAsRecords + [{'value': 'Austria', 'key': 'at'}, {'value': 'Germany', 'key': 'de'}] + + >>> countries.getRowsByValue('value', 'Germany') + [{'value': 'Germany', 'key': 'de'}] + Caching ======= diff --git a/table.py b/table.py index d09c194..fcece94 100644 --- a/table.py +++ b/table.py @@ -106,6 +106,9 @@ class DataTable(AdapterBase): result.append(item) return result + def getRowsByValue(self, column, value): + return [r for r in self.dataAsRecords if r[column] == value] + TypeInterfaceSourceList.typeInterfaces += (IDataTable,) From c824c0bdb574106b534663fb0177c819e1782251 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 16 Mar 2015 16:31:52 +0100 Subject: [PATCH 070/269] provide new 'contact' states definition to be used for persons or other kinds of parties --- organize/stateful/README.txt | 6 ++++ organize/stateful/browser.py | 5 +-- organize/stateful/configure.zcml | 14 ++++++++ organize/stateful/contact.py | 56 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 organize/stateful/contact.py diff --git a/organize/stateful/README.txt b/organize/stateful/README.txt index 11182e4..be98378 100644 --- a/organize/stateful/README.txt +++ b/organize/stateful/README.txt @@ -187,6 +187,12 @@ Task States >>> from loops.organize.stateful.task import taskStates, publishableTask +Contact States +=========== + + >>> from loops.organize.stateful.contact import contactStates + + Fin de partie ============= diff --git a/organize/stateful/browser.py b/organize/stateful/browser.py index 24604f7..6dd432f 100644 --- a/organize/stateful/browser.py +++ b/organize/stateful/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -45,7 +45,8 @@ template = ViewPageTemplateFile('view_macros.pt') statefulActions = ('classification_quality', 'simple_publishing', 'task_states', - 'publishable_task',) + 'publishable_task', + 'contact_states',) def registerStatesPortlet(controller, view, statesDefs, diff --git a/organize/stateful/configure.zcml b/organize/stateful/configure.zcml index 30ad487..1c6ebae 100644 --- a/organize/stateful/configure.zcml +++ b/organize/stateful/configure.zcml @@ -77,6 +77,20 @@ set_schema="cybertools.stateful.interfaces.IStateful" /> + + + + + + + + Date: Tue, 17 Mar 2015 08:50:53 +0100 Subject: [PATCH 071/269] don't show priority and activity if corresponding data tables do not exist; translations --- README.txt | 2 +- locales/de/LC_MESSAGES/loops.mo | Bin 25942 -> 26265 bytes locales/de/LC_MESSAGES/loops.po | 23 ++++++++++++++++++++++- organize/work/browser.py | 14 ++++++++++---- organize/work/work_macros.pt | 17 +++++++++-------- table.py | 3 +-- 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/README.txt b/README.txt index bbffa03..9da5ed7 100755 --- a/README.txt +++ b/README.txt @@ -913,7 +913,7 @@ relates ISO country codes with the full name of the country. >>> sorted(adapted(concepts['countries']).data.items()) [('at', ['Austria']), ('de', ['Germany'])] - >>> countries.dataAsRecords + >>> countries.dataAsRecords() [{'value': 'Austria', 'key': 'at'}, {'value': 'Germany', 'key': 'de'}] >>> countries.getRowsByValue('value', 'Germany') diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index df8afd747c7f0c1ec989f7ee6b9d21c84e029d75..8067eef3cf1a1af1c730a4a8073b8781270d3692 100644 GIT binary patch delta 9647 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 OYFbuw-lD-D`~44G@)?W( delta 9357 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 tpsaa5p3\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -1009,6 +1009,21 @@ msgstr "Kalender" msgid "Work Items" msgstr "Aktivitäten" +msgid "Work Item Type" +msgstr "Art der Aktivität" + +msgid "Unit of Work" +msgstr "Standard-Aktivität" + +msgid "Scheduled Event" +msgstr "Termin" + +msgid "Deadline" +msgstr "Deadline" + +msgid "Check-up" +msgstr "Überprüfung" + msgid "Work Items for $title" msgstr "Aktivitäten für $title" @@ -1039,6 +1054,12 @@ msgstr "Dauer/Aufwand" msgid "Duration / Effort (hh:mm)" msgstr "Dauer / Aufwand (hh:mm)" +msgid "Priority" +msgstr "Priorität" + +msgid "Activity" +msgstr "Tätigkeit" + msgid "Action" msgstr "Aktion" diff --git a/organize/work/browser.py b/organize/work/browser.py index ca8d058..b5270b3 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -549,13 +549,19 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): @Lazy def priorities(self): - prio = self.conceptManager.get('organize.work.priorities') - return prio and adapted(prio).dataAsRecords or [] + if 'priority' in self.workItemType.fields: + prio = self.conceptManager.get('organize.work.priorities') + if prio: + return adapted(prio).dataAsRecords() + return [] @Lazy def activities(self): - act = self.conceptManager.get('organize.work.activities') - return act and adapted(act).dataAsRecords or [] + if 'activity' in self.workItemType.fields: + act = self.conceptManager.get('organize.work.activities') + if act: + return adapted(act).dataAsRecords() + return [] @Lazy def duration(self): diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index abc9682..7120d08 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -163,29 +163,30 @@ tal:attributes="value view/deadlineTime" />
+ tal:define="priorities view/priorities; + activities view/activities" + tal:condition="python:priorities or activities"> - - - - From 60604cd38b3be3b336254086694e576d018e1ecf Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 30 Mar 2015 13:47:04 +0200 Subject: [PATCH 089/269] allow overriding of default resource relation views and sorting --- browser/concept.py | 10 ++++++---- browser/concept_macros.pt | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/browser/concept.py b/browser/concept.py index 3e7e6ab..1b1003b 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -457,7 +457,7 @@ class ConceptView(BaseView): if r.order != pos: r.order = pos - def getResources(self): + def getResources(self, relView=None, sort='default'): form = self.request.form #if form.get('loops.viewName') == 'index.html' and self.editable: if self.editable: @@ -466,13 +466,15 @@ class ConceptView(BaseView): tokens = form.get('resources_tokens') if tokens: self.reorderResources(tokens) - from loops.browser.resource import ResourceRelationView + if relView is None: + from loops.browser.resource import ResourceRelationView + relView = ResourceRelationView from loops.organize.personal.browser.filter import FilterView fv = FilterView(self.context, self.request) - rels = self.context.getResourceRelations() + rels = self.context.getResourceRelations(sort=sort) for r in rels: if fv.check(r.first): - view = ResourceRelationView(r, self.request, contextIsSecond=True) + view = relView(r, self.request, contextIsSecond=True) if view.checkState(): yield view diff --git a/browser/concept_macros.pt b/browser/concept_macros.pt index 0e725c8..0386eb3 100644 --- a/browser/concept_macros.pt +++ b/browser/concept_macros.pt @@ -370,4 +370,20 @@ + + + + + + + + From 396e17c0dc95134460dfc1b18428f862a649d588 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 5 Apr 2015 10:40:07 +0200 Subject: [PATCH 090/269] allow entering of survey data for other person; prerequisite: keep URL params in title and breadcrumbs links --- browser/common.py | 10 ++++++++++ browser/concept_macros.pt | 2 +- browser/node.py | 3 +++ knowledge/survey/browser.py | 19 +++++++++++++++++-- knowledge/survey/view_macros.pt | 6 +++++- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/browser/common.py b/browser/common.py index 5936abf..dcfaf13 100644 --- a/browser/common.py +++ b/browser/common.py @@ -252,6 +252,16 @@ class BaseView(GenericView, I18NView, SortableMixin): result.append(view) return result + @Lazy + def urlParamString(self): + return self.getUrlParamString() + + def getUrlParamString(self): + qs = self.request.get('QUERY_STRING') + if qs: + return '?' + qs + return '' + @Lazy def principalId(self): principal = self.request.principal diff --git a/browser/concept_macros.pt b/browser/concept_macros.pt index 0386eb3..ca396ed 100644 --- a/browser/concept_macros.pt +++ b/browser/concept_macros.pt @@ -51,7 +51,7 @@

Title + tal:attributes="href string:${request/URL}${item/urlParamString}"> Back to Questionnaire
@@ -67,6 +67,10 @@

+ +
+ + - - - - - - - -
- -
- - - -
- -
- -
- - - -
-
-
- -
- -
-
-
- -
- / -
-
+ +
+ + + + + + + + +
+ +
+ + + +
+
+
+ +
+ + - +
+
+
+ +
+ +
+
+
+ +
+ / +
From 74eb054bc9e98b65063941e38517240bc5d7b617 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 20 Mar 2015 08:03:14 +0100 Subject: [PATCH 075/269] filter qualification reports by contact state --- knowledge/qualification/report.py | 23 ++++++++++++++++++----- locales/de/LC_MESSAGES/loops.mo | Bin 26265 -> 26518 bytes locales/de/LC_MESSAGES/loops.po | 20 +++++++++++++++++++- organize/stateful/contact.py | 6 +++--- organize/work/report.py | 12 ++++++++++++ 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/knowledge/qualification/report.py b/knowledge/qualification/report.py index 494bb97..0bbc0b0 100644 --- a/knowledge/qualification/report.py +++ b/knowledge/qualification/report.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -22,6 +22,7 @@ Qualification management report definitions. from zope.cachedescriptors.property import Lazy +from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria from cybertools.util.jeep import Jeep from loops.expert.report import ReportInstance from loops.organize.work.report import WorkRow @@ -39,13 +40,12 @@ class QualificationOverview(ReportInstance): rowFactory = WorkRow - fields = Jeep((task, party, workTitle, dayStart, dayEnd, state,)) - #partyState,)) # +deadline? + fields = Jeep((task, party, workTitle, dayStart, dayEnd, state, + partyState,)) # +deadline? - defaultOutputFields = fields + defaultOutputFields = Jeep(list(fields)[:-1]) defaultSortCriteria = (party, task,) - def getOptions(self, option): return self.view.options(option) @@ -53,6 +53,14 @@ class QualificationOverview(ReportInstance): def states(self): return self.getOptions('report_select_state' or ('planned',)) + @property + def queryCriteria(self): + crit = self.context.queryCriteria or [] + f = self.fields.partyState + crit.append( + LeafQueryCriteria(f.name, f.operator, 'active', f)) + return CompoundQueryCriteria(crit) + def selectObjects(self, parts): result = [] workItems = self.recordManager['work'] @@ -112,6 +120,11 @@ class PersonQualifications(QualificationOverview): def getOptions(self, option): return self.view.typeOptions(option) + @property + def queryCriteria(self): + crit = self.context.queryCriteria or [] + return CompoundQueryCriteria(crit) + def selectObjects(self, parts): workItems = self.recordManager['work'] person = self.view.context diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 8067eef3cf1a1af1c730a4a8073b8781270d3692..0b5e42c40c4c84b9df3828c1a5628129e8302f72 100644 GIT binary patch delta 9723 zcmZA534Bg>zQ^$sNk~YDAV@=Emsnya2%$)=*=nyniG68AEL9Jxrq-%7Y8Ne4Y7a8j z)<|2_Xs75cN;@Xg+B)u3xt&(W{d}ME%e;E;dG+_a|NsB_pZ|WIC$;X+K5IVn@!Smc z%X7Gn`8ZAl-Vb)1lv0j!;u)24)pML=@WDoyiluR$`6`wnzZ?VbP4vZ0SPr)$O*(rp z5|3jDUdKSk@i_NM1X1u9%cF07Hy?^>C=%6B9V?H;D&#w27^b2+7>_jQ%(VLp%(t*K z<$F*AJY@Nk7)bw45s6CNxM&q_S%rI8f$|?wGc47>ani6X_Q2t&2Dh7g(Vu)Fmcf%& z{*mP`qWbv~L-DTe)4%f@35~pTLw9LHP%Bc)Y>ENoJ77iZhNW=;s@^cv^AqfTHfn_y zqn3Ijs^dLa6;Gquxr!dm@E!?O{1t0sP_*Mz$5^b4Jy8!1M>U*-YH&Gf3-VD77NXiY zXZdSbjr@JojQwNWt*VCF>iRLPzh=;y0u7`qs^L@&$4pd*3s3`k3pJ1fR(=ju{~Odm zAEN37a8hewZB%(TR6B#Q22Mo{XhkE|Uk&6_PzMho|2bdrp&9u#cK0$21IX7zbEj%CDdXa1*s+ zKck)tXyWFBQ4^|x>aaO#A_*9Tsi-X)fqLFEjYKes`KSk0qLyY8YRUGa8vGdb;5Af( z_fSjw8*1jEP2KwusCJrQFm|-^UZ?>KweoRTR_}j~6)ZsQ`Rk~W?Lw{0F}r^nwX~mM z8N7sAsq3hL6{DVeggTV|vF_^_fm-s~sDU;`J=e}F``?8`1qynhmU0NH;s{iS<52^d zj9Q6oY=y5``AJm$BGim8nqOm0@^?{(F|e6C@J41!^way_K?zJiHPjt7!X(QNLJeRv zYUblnXW&KD7tS)&K;J+OFb_j;C#v2tOvlTpt&3~!Ze4HmzW*ah=)q}N5f`DBa1Clk zd$2qnN4UY8_ z=;>n>Mx&N!B5IFkS@|l|R;;u9JE)oLw)|yOhc{5q-$G5~0cyZcQ7h)(*4^SD)LE+L z<~>dW5?YFusF5b29vFnGI0E@vcc!5Bb{*GK9iwpuY9L#%BEE}i=#R)BNX{wL7M(`bJBM20&ruy0qdL5U zdhQXb<6lu{#y^g{-v1C1DhNmIO+94QoR+ALC!jiRfTA3&4?YM(Gpb+#_=0+F^y@rkKMy%NyHKPuw2a`~HJOnkMv8WZ8fSPd@ z>QK!@)mve%N7dhfs<#XE+`bO1e>D%-koU=)N_4M&-F(QbVxkwuR}JC0(IcA8%t3SyEl_ zHe?LW`&b?Cpq}&X>`o{g)nPR1`R1sz)E?DeAIp0NkkF|ghk9>kTYd>@Yt~zN0cz>a zpayalHIR#_nO;G4bQ`s0|3tM{I>EgkhUzC8)lPe4f*z+c2|d^g)leF0q?xD(W?6m- zYNi`dD{~k%kPpp|QSDqn4eSbP1-?dgSd2Q9cTmqgMqj=EPe|y7)5WbAjCwHC3`f1k zwJqNiHLy0QrA)N)6mziMAB7rF7HU8*p|*G-s{OUPPybFn35{r{-PmUqqDKA!YDJ1r zhw)3)p)EEanx(qBSbrrRQlN$dySrx~ z6!pbZ4|S^J(A$BPXQGyV4r&04Q1w=!4&4q^dxfZaA6ouAYCxB){9bp~Ulkrvpgnzx zYA~b+KRg(LjLn&gTKd!GXQ&mpfqMS7<$px&?Qf{Jz(3J#FB~NVSA<)5HNdeiPd#7Oexdb$lZLJcGy^;|Nl{sb%cn5$6}*p6E10%S`)P7#S( z6kJ1%{0TO|s=eIb_s*yprsFfX5LIt8*2JTz`d^_2;PiIug`-xm4XS<`YJyWR8W&@G zz5n}3=&;?x0DOvNF`$n-(@N-1z9njl;!rb5K%MG-sOM76p{Nin1q%pq@TNlO;7_$L=9jhs)JdmLzIiBC6??cr;j9TiWsFnB&YKw|72cM!Q zkdx|e#bVUVH=^FA{irSWTqL1}zQ>CA6g9JoY3|{PLfvnRx}Su44M$-pzKp@R3bg{8 zQ8PY`TCuZO2|q^-ycmPB7C|Id=pl3hYA=`~cxKcHs*2-V=P zs8d{bko)5li#k-VqxSSotbzwoD|Z&v-c>8VkD7U@!S0GzK#xXJgM1-10wqMo~hs{eE_`>%>+hqxmR!;a*m zu{vgARh*B_aV=`k&!f)FeQbfhpa$4vs5_w6sQVo)-^)xf2Vphtj~L4O>zi*D1sX^m zYRR^umi`dxd*PVnPog?HgO%`8%YS2jkDBQN)Jpw~s#l5=+XTZ<&v!$$-^W9uEs0cA z$4gNiZbBWdeAKVp6>N_`;dG20#xwX1M&o1jz6HbGrR{+lND8W>A*dA`iQ1CYR_@tK zLVK|T_1Yao?P(FJ;-{zqT|+f^1N9z%htJ?$)C^0HaNmw_)XZa0^;=naJnE44#hy43 zKh^txn1q&O!ASSJdp2w~jKFXcxFx0@upx%xw)C6au zR(K)mEy%-a^zXb)LJb~AJy?Wo@DggoWksbKsCG7^4*A=t0Uyd>{nfy6D>#Fi*?H8GT|#wKZ2p8ApkJmtz);j)MxZ)w zW#tK|FR)&gPe=7L1@(N6xyWM`R--!Jh#KK`RL6y=_x2O4jQ>I{W%;r0S91+igMCmP z3_>F%(2&H+*C_2b2GAz5n|5`y-**m0Ux)xrpdN!4;wk>4wBALf1f( z^W>cHmbgF3bx9Xmz7f`=o>uD(D|;2EQ1+@le_ii?4Kn^jBO;FYGnHPq2TFLE@+>mj zv6dAsB>g%ujQk>kFGA-GkwA>1ya&M#o%br{^9iw)GF^J@-y+@C!^b*rk^6BI3Md*x&7MrKc_qg{zq+hi9I(%zM@38yxNY^A?7H_B?p|5Yx^L(5pt`fQu zsgOq`5V|T7_lXqCYrwiX5YtFU5c#BMTDcD{CPIlEVk-B><6&Ys=~qx!Q(}P@;!h-= zBPMbq7NdxgE1$A?mf1#ntEH!#O(>f~thBto^>&eOLyRT#1{_3P7YTkm|8pJkma`H6 zrsxC&^u?rWvgJQ^v;X=0nN)AP*Ar)upKp0piX$eG-#`p0 zSx((}kLX9C-qXFrIMUsTNa6$X!>|RuM!Zkx(%%42y(#x&J$1H_EW|Z(UW+Es6+Xi=tp!WU5nU2`u|_) z_WTiijtC(7_^|(PQF9Q{gqTbWC3N|_ct3YgS%3ZX`dyilA6fnw_uE-|Kk2!o%Eo!??-FOOA#ekf6}GN%qb~AU!u9y z*^M2^-y`&|Y`T6WvPiGi{&%V0S1S3=h+g1;TS{d)_-sif-{VzxKs{=HU{@_OXou`-cKjIw-V z?xzw#zlGe4; z^us@pkHS6pGEtv6OXzAw`E9&L93r|BuTl0UagOu~Y^C{^BT=3xLkuUD5fzAciA~(^ zLX=$pBvFOu+ z7Ig&>&Alo2<3sqA_`92RR$wC0i)TK;c35EdDwF>G6+(Ws6^z4K-ZW3Y#El?Twg+aC zyiBwr{}t}VOBjy>aDmm!Hp6gk!K-y6s{4-0&dA*m|1@~&l`WPA<H^JjOJtMo|=Y$GArCKyA2<*Br(sz7DR>3Qy z2Kof|R+AI6)#8ll1y@I>`h?NUL>`Rdp*a~-)T*+%G3mzwCZ0JlB{O^c*xbLRZwsC? SBkRnGF%#Wt1&cE(`TQ^M9~d(L delta 9487 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 Vy7=S;4}LH2\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -1134,6 +1134,9 @@ msgstr "Bemerkung" msgid "desc_transition_comments" msgstr "Notizen zum Statusübergang." +msgid "contact_states" +msgstr "Kontaktstatus" + # state names msgid "accepted" @@ -1202,6 +1205,12 @@ msgstr "unklassifiziert" msgid "verified" msgstr "verifiziert" +msgid "prospective" +msgstr "künftig" + +msgid "inactive" +msgstr "inaktiv" + # transitions msgid "accept" @@ -1276,6 +1285,15 @@ msgstr "verifizieren" msgid "work" msgstr "bearbeiten" +msgid "activate" +msgstr "aktivieren" + +msgid "inactivate" +msgstr "inaktiv setzen" + +msgid "reset" +msgstr "zurücksetzen" + # calendar msgid "Monday" diff --git a/organize/stateful/contact.py b/organize/stateful/contact.py index 77f0ed8..d4db17b 100644 --- a/organize/stateful/contact.py +++ b/organize/stateful/contact.py @@ -40,12 +40,12 @@ def contactStates(): return StatesDefinition('contact_states', State('prospective', 'prospective', ('activate', 'inactivate',), color='blue'), - State('active', 'active', ('retract', 'inactivate',), + State('active', 'active', ('reset', 'inactivate',), color='green'), - State('inactive', 'inactive', ('activate',), + State('inactive', 'inactive', ('activate', 'reset'), color='x'), Transition('activate', 'activate', 'active'), - Transition('retract', 'retract', 'prospective'), + Transition('reset', 'reset', 'prospective'), Transition('inactivate', 'inactivate', 'inactive'), initialState='active') diff --git a/organize/work/report.py b/organize/work/report.py index 648a4af..65e77d7 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -81,6 +81,18 @@ class DurationField(Field): class PartyStateField(StateField): + def getValue(self, row): + context = row.context + if context is None: + return None + party = util.getObjectForUid(context.party) + ptype = adapted(party.conceptType) + stdefs = IOptions(ptype)('organize.stateful') or [] + if self.statesDefinition in stdefs: + stf = getAdapter(party, IStateful, + name=self.statesDefinition) + return stf.state + def getContext(self, row): if row.context is None: return None From 82645fb574e106093b29d33d3e3fb3a828cbb41a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 20 Mar 2015 11:38:47 +0100 Subject: [PATCH 076/269] consequently use reporting view base class instead of specialized class; + other improvements --- expert/browser/configure.zcml | 6 ++++++ expert/browser/export.py | 20 ++++++++++++++++++-- expert/browser/report.py | 2 ++ knowledge/qualification/browser.py | 7 +++---- knowledge/qualification/configure.zcml | 6 ------ 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/expert/browser/configure.zcml b/expert/browser/configure.zcml index e5804f2..95244a8 100644 --- a/expert/browser/configure.zcml +++ b/expert/browser/configure.zcml @@ -99,4 +99,10 @@ factory="loops.expert.browser.report.EmbeddedResultsConceptView" permission="zope.View" /> + + diff --git a/expert/browser/export.py b/expert/browser/export.py index 85f2305..41870d4 100644 --- a/expert/browser/export.py +++ b/expert/browser/export.py @@ -69,8 +69,7 @@ class ResultsConceptCSVExport(ResultsConceptView): value = f.getValue(row) if ILoopsObject.providedBy(value): value = value.title - if isinstance(value, unicode): - value = value.encode(self.encoding) + value = encode(value, self.encoding) data[f.name] = value writer.writerow(data) text = output.getvalue() @@ -86,3 +85,20 @@ class ResultsConceptCSVExport(ResultsConceptView): response.setHeader('Pragma', '') response.setHeader('Content-Type', 'text/csv') response.setHeader('Content-Length', len(text)) + + +def encode(text, encoding): + if not isinstance(text, unicode): + return text + try: + return text.encode(encoding) + except UnicodeEncodeError: + result = [] + for c in text: + try: + result.append(c.encode(encoding)) + except UnicodeEncodeError: + result.append('?') + return ''.join(result) + return '???' + diff --git a/expert/browser/report.py b/expert/browser/report.py index 395d5f3..b8e1216 100644 --- a/expert/browser/report.py +++ b/expert/browser/report.py @@ -193,6 +193,8 @@ class ResultsConceptView(ConceptView): @Lazy def downloadLink(self, format='csv'): opt = self.options('download_' + format) + if not opt: + opt = self.typeOptions('download_' + format) if opt: return opt[0] diff --git a/knowledge/qualification/browser.py b/knowledge/qualification/browser.py index 5c0a061..4a98f70 100644 --- a/knowledge/qualification/browser.py +++ b/knowledge/qualification/browser.py @@ -34,10 +34,9 @@ from loops.util import _ class Qualifications(ResultsConceptView): - reportName = 'qualification_overview' + # obsolete because we can directly use ResultsConceptView + #reportName = 'qualification_overview' -class QualificationsCSVExport(ResultsConceptCSVExport): - - reportName = 'qualification_overview' + pass # report assigned to query via hasReport relation diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml index b6a1a5e..e4f02c9 100644 --- a/knowledge/qualification/configure.zcml +++ b/knowledge/qualification/configure.zcml @@ -23,12 +23,6 @@ factory="loops.knowledge.qualification.browser.Qualifications" permission="zope.View" /> - - Date: Sat, 21 Mar 2015 09:18:53 +0100 Subject: [PATCH 077/269] fix start action: only for work; repair IFTA qualification work items: done -> running --- organize/work/browser.py | 23 +++++++++++++++++++++++ organize/work/configure.zcml | 8 ++++++++ 2 files changed, 31 insertions(+) diff --git a/organize/work/browser.py b/organize/work/browser.py index b5270b3..478dab5 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -28,6 +28,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from zope.event import notify from zope.lifecycleevent import ObjectModifiedEvent +from zope.security.proxy import removeSecurityProxy from zope.traversing.browser import absoluteURL from zope.traversing.api import getName, getParent @@ -733,3 +734,25 @@ def formatTimeDelta(value): return str(int(round(h / 24.0))) return u'%02i:%02i' % (h, m) + +class FixCheckupWorkItems(object): + + def __call__(self): + context = removeSecurityProxy(self.context) + rm = context['records']['work'] + count = 0 + workItems = list(rm.values()) + for wi in workItems: + if wi.state in ('done',): + if wi.workItemType != 'checkup': + print '*** done, but not checkup', wi.__name__ + continue + wi.state = 'running' + wi.reindex('state') + if wi.end == wi.start: + del wi.data['end'] + count += 1 + msg = '*** checked: %i, updated: %i.' % (len(workItems), count) + print msg + return msg + diff --git a/organize/work/configure.zcml b/organize/work/configure.zcml index d4eab19..28d8d54 100644 --- a/organize/work/configure.zcml +++ b/organize/work/configure.zcml @@ -132,6 +132,14 @@ attribute="embed" permission="zope.View" /> + + + + Date: Sat, 21 Mar 2015 10:42:25 +0100 Subject: [PATCH 078/269] allow viewing report 'as such' (without results, but children) --- expert/browser/report.pt | 10 ++++++++-- expert/browser/report.py | 36 +++++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/expert/browser/report.pt b/expert/browser/report.pt index 15caa68..94c6966 100644 --- a/expert/browser/report.pt +++ b/expert/browser/report.pt @@ -3,7 +3,8 @@
@@ -20,7 +21,12 @@ i18n:attributes="value" />
-
+ +
+ + +
+
diff --git a/expert/browser/report.py b/expert/browser/report.py index b8e1216..c3e1456 100644 --- a/expert/browser/report.py +++ b/expert/browser/report.py @@ -45,6 +45,8 @@ class ReportView(ConceptView): """ A view for defining (editing) a report. """ + resultsRenderer = None # to be defined by subclass + @Lazy def report_macros(self): return self.controller.getTemplateMacros('report', report_template) @@ -57,6 +59,25 @@ class ReportView(ConceptView): def dynamicParams(self): return self.request.form + @Lazy + def report(self): + return self.adapted + + @Lazy + def reportInstance(self): + instance = component.getAdapter(self.report, IReportInstance, + name=self.report.reportType) + instance.view = self + return instance + + @Lazy + def queryFields(self): + ri = self.reportInstance + qf = ri.getAllQueryFields() + if ri.userSettings: + return [f for f in qf if f in ri.userSettings] + return qf + class ResultsView(NodeView): @@ -105,13 +126,6 @@ class ResultsView(NodeView): def report(self): return adapted(self.virtualTargetObject) - @Lazy - def reportInstance(self): - instance = component.getAdapter(self.report, IReportInstance, - name=self.report.reportType) - instance.view = self - return instance - #@Lazy def results(self): return self.reportInstance.getResults(self.params) @@ -221,11 +235,3 @@ class ReportConceptView(ResultsConceptView, ReportView): @Lazy def macro(self): return self.report_macros['main'] - - @Lazy - def queryFields(self): - ri = self.reportInstance - qf = ri.getAllQueryFields() - if ri.userSettings: - return [f for f in qf if f in ri.userSettings] - return qf From d317612187996e2e9bb93bad6240a80a96ec800d Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 21 Mar 2015 17:12:16 +0100 Subject: [PATCH 079/269] sorting by link on column header on reporting results --- browser/common.py | 50 ++++++++++++++++++++++++++++++++++++++- expert/browser/report.py | 13 ++++++++++ expert/browser/results.pt | 44 +++++++++++++++++++++------------- expert/report.py | 4 +++- 4 files changed, 93 insertions(+), 18 deletions(-) diff --git a/browser/common.py b/browser/common.py index f019673..f216f5e 100644 --- a/browser/common.py +++ b/browser/common.py @@ -132,7 +132,55 @@ class EditForm(form.EditForm): return parentUrl + '/contents.html' -class BaseView(GenericView, I18NView): +class SortableMixin(object): + + @Lazy + def sortInfo(self): + result = {} + for k, v in self.request.form.items(): + if k.startswith('sortinfo_'): + tableName = k[len('sortinfo_'):] + if ',' in v: + fn, dir = v.split(',') + else: + fn = v + dir = 'asc' + result[tableName] = dict(colName=fn, ascending=(dir=='asc')) + return result + + def isSortableColumn(self, tableName, colName): + return False # overwrite in subclass + + def getSortUrl(self, tableName, colName): + url = str(self.request.URL) + paramChar = '?' in url and '&' or '?' + si = self.sortInfo.get(tableName) + if si is not None and si.get('colName') == colName: + dir = si['ascending'] and 'desc' or 'asc' + else: + dir = 'asc' + return '%s%ssortinfo_%s=%s,%s' % (url, paramChar, tableName, colName, dir) + + def getSortParams(self, tableName): + url = str(self.request.URL) + paramChar = '?' in url and '&' or '?' + si = self.sortInfo.get(tableName) + if si is not None: + colName = si['colName'] + dir = si['ascending'] and 'asc' or 'desc' + return '%ssortinfo_%s=%s,%s' % (paramChar, tableName, colName, dir) + return '' + + def getSortImage(self, tableName, colName): + si = self.sortInfo.get(tableName) + if si is not None and si.get('colName') == colName: + if si['ascending']: + return '/@@/cybertools.icons/arrowdown.gif' + else: + return '/@@/cybertools.icons/arrowup.gif' + + +class BaseView(GenericView, I18NView, SortableMixin): actions = {} portlet_actions = [] diff --git a/expert/browser/report.py b/expert/browser/report.py index c3e1456..ed8c892 100644 --- a/expert/browser/report.py +++ b/expert/browser/report.py @@ -192,6 +192,13 @@ class ResultsConceptView(ConceptView): ri = component.getAdapter(self.report, IReportInstance, name=reportType) ri.view = self + if not ri.sortCriteria: + si = self.sortInfo.get('results') + if si is not None: + fnames = (si['colName'],) + ri.sortCriteria = [f for f in ri.getSortFields() + if f.name in fnames] + ri.sortDescending = not si['ascending'] return ri def results(self): @@ -212,6 +219,12 @@ class ResultsConceptView(ConceptView): if opt: return opt[0] + def isSortableColumn(self, tableName, colName): + if tableName == 'results': + if colName in [f.name for f in self.reportInstance.getSortFields()]: + return True + return False + class EmbeddedResultsConceptView(ResultsConceptView): diff --git a/expert/browser/results.pt b/expert/browser/results.pt index 62a9085..e7252de 100644 --- a/expert/browser/results.pt +++ b/expert/browser/results.pt @@ -35,38 +35,50 @@
-
+

Download Data + tal:define="params python:item.getSortParams(tableName)" + tal:attributes="href string:${item/downloadLink}$params">Download Data

- - + - +
+ + + + + +
- - + +
- - + +
diff --git a/expert/report.py b/expert/report.py index bbab6ac..2b75172 100644 --- a/expert/report.py +++ b/expert/report.py @@ -120,7 +120,9 @@ class ReportInstance(BaseReport): 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(), + sortDescending=self.sortDescending, + queryCriteria=qc, limits=limits) def selectObjects(self, parts): From 0316bafffc673716a184285a6f59af0194271181 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 22 Mar 2015 07:10:16 +0100 Subject: [PATCH 080/269] add title to sortable columns; + title on sort link --- expert/browser/results.pt | 9 +++++---- locales/de/LC_MESSAGES/loops.mo | Bin 26518 -> 26583 bytes locales/de/LC_MESSAGES/loops.po | 9 ++++++++- organize/work/report.py | 11 +---------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/expert/browser/results.pt b/expert/browser/results.pt index e7252de..f3472b4 100644 --- a/expert/browser/results.pt +++ b/expert/browser/results.pt @@ -51,10 +51,11 @@
- + diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 0b5e42c40c4c84b9df3828c1a5628129e8302f72..396cbca0436a66e515e1d6ea55069230c1947e6b 100644 GIT binary patch delta 7278 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 delta 7227 zcmXZf2Y8po9l-H7K-eP$WP}y=CTwJi$dC<*AVa1M#f^fv_yeW1EC(^Dhzt=e3L*xL zOie`v zPhg2CN<U^&`c~}g0h4xQE z{UEx|mzav@{m%N)?-Y!@XrnkaDQHG&2U}oq>YcGX_Qaw%9PKv-9X~C6e+13Y0yNbt z(SW8(STk+1KASV_o4m2MFYKz_AAa! zt&Mfi_MYfGBe4cPfCjX*DfxGTH8j-4t;m0(ulOH#RHRvamX)wL^_u7c4MY8AEJD2> zmcYU2r5zc*j|omecl-dB!r7s|C_%xqdL7 zyU_l7&>bHPeuFispGPlaiI#ETO@nQ)i0{9%1?-Ma)C-MpaHx+!1DJ^J{BHCPJcRz> zEJg!;4h=8|Q*Z;?Z#z!Hqv+9fY!x3}e=K&;gI3nOGKFiDu|^bmF%{ zeH*f=Xg3bTOPGpX+Z6oWiiV&I&O|TY?9jd#Q>brgL;mgX*U)i)==e7@wcnzV{tI2` z-{?+iaRGcC4Uk(LbwU4n_QB>j2^--u{@w5fObiGeCZZ|22R-9i zq5WC(C|(Nnb?8nuhWb%-!IS9t)96Mnp#fh0org z2(;rkEHjX&|xfk zDJEfKoPh?i8q4GB=tLhN|AG|lMvo{T?Y9q2@nLk~Gw6cn&~aDLg?~fuOmau+zW)>o zHl(3vQy|5fc?G^4xFKj&YgnYo4q3wMqK zO2I@$K2)OMGi(|@WCYuxJL-%M9E_gvC^Vp{Xa=UCJD!eSsz=d&OM@%W{%@fD-bBZ3 z?o9rxP{^af=XDqtVp5m5!(ueRY&3wspgYM$C)$ZloR6k<5B9=?$Zt|qv1`23-srdi z=(u5Mprg8ye=pgCG`K(_d{~4ISc4wP25g3TSPRdfXI`XR+^ws7PQ}h1O+3{LL(Z3mGEISphf6_9P|>cLI=Kv#1OrORq-4;uJBFqhSJal8>8b} zp?9ehy54|LPYkEvt-lL>w~vMTLiA`>g!WuCbswXF>_r1Pi0;eM0-t;K=ZO0vgbCG@vKY zBc6xO|DxYnKUzbhpt#&Xkb^$6O`-iWG}2Sy`(>;}y;R?L!lq~-UD0tv z(Eigxdm^|D-N1S@)49lzCZatQYSVBWjrTS>?>WJ>7J9?`J zq2n@xqtP8s!qPYcy_EB?7%oHSUx8&J=)cw{eARk4g~*!2~&2Jf~iUw6sNE`8c-iJfbr-8v(QVFjb(8? zI^ovfKD7TSG{CE9W=ju_Gm?(hTcJnZYcTmY!l^Wvn%P(u7hn<039bmP#thnDLw9%- z4d4g#Xs(5N;UV#}tAfs34^4S9G=L6hhWig8|0O64rJ)dx#)4;v<)}}_t~djoC>K5J z9cZ9?f=93p_0w1%lZVFrnxgZyMK{zr*d0qz@0Fn7B^rtbG7Mc{T<|V5fQQhCH>3UE zLsPvC&BPbz5uL%Acpcrq%*^;G7N9#{iN26GBkhYVa2OiUe6-&ZbfM*F;2Y6#xuL!r9sdb>6knjJ{|+5@4()$^ByRbdfccBY?j1}{oXr_Ka`xWBGHpfcn_@3ze z0}>QEP{>3VUW6|A3VONLpnv6#VJG|il zN3txmC)QH%EZ#t$-Mi?S?m;_#js|obo$w_3j=#rhcplwh(Q)zXNJDqt1nu88w0A`> z=|Jp@_u%Kg|Mw`ElDXsK-|~g%*{wn++K7IC9}Q$TI&nS@#{Jj>(r+paE{d zLbwxM_(SyfU?29x<7mHHlgNJy3QZ_f$C2p5GtmxzKqr1WxEy`ItI&*XMFZM}?(`@+ z?nLA}a* zBbbLp@M(13Y;>Mg=p}y@4S4Hh^6vyYLc_=C&i11zJAy8BCipWNV38?tfT`$NR!0|Z z8``_0KVbbreG\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -89,6 +89,9 @@ msgstr "Thema ändern" msgid "Please correct the indicated errors." msgstr "Bitte berichtigen Sie die angezeigten Fehler." +msgid "tooltip_sort_column" +msgstr "Nach dieser Spalte sortieren" + # expert (reporting) msgid "Download Data" @@ -509,6 +512,8 @@ msgstr "Wer?" msgid "When?" msgstr "Wann?" +# personal stuff + msgid "Favorites" msgstr "Lesezeichen" @@ -533,6 +538,8 @@ msgstr "Anmelden" msgid "Presence" msgstr "Anwesenheit" +# general + msgid "Actions" msgstr "Aktionen" diff --git a/organize/work/report.py b/organize/work/report.py index 65e77d7..d80a4e7 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -154,7 +154,7 @@ party = TargetField('userName', u'Party', executionSteps=['query', 'sort', 'output']) workTitle = Field('title', u'Title', description=u'The short description of the work.', - executionSteps=['output']) + executionSteps=['sort', 'output']) workDescription = Field('description', u'Description', description=u'The long description of the work.', executionSteps=['output']) @@ -209,19 +209,10 @@ class WorkRow(BaseRow): value = self.getDuration(attr) return value - def xx_getPartyState(self, attr): - party = util.getObjectForUid(self.context.party) - ptype = adapted(party.conceptType) - for std in IOptions(ptype)('organize.stateful') or []: - stf = getAdapter(party, IStateful, name=std) - return stf.state - return None - attributeHandlers = dict(day=getDay, dayStart=getStart, dayEnd=getEnd, dayFrom=getDay, dayTo=getDay, duration=getDuration, effort=getEffort,) - #partyState=getPartyState) class WorkReportInstance(ReportInstance): From 7113369fcacdf4b61f34c7c8de70760ec423dc57 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 23 Mar 2015 13:49:53 +0100 Subject: [PATCH 081/269] breadcrumbs improvements --- browser/common.py | 6 +++++- browser/concept.py | 8 ++++++++ browser/node.py | 9 ++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/browser/common.py b/browser/common.py index f216f5e..850fae1 100644 --- a/browser/common.py +++ b/browser/common.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -377,6 +377,10 @@ class BaseView(GenericView, I18NView, SortableMixin): def isPartOfPredicate(self): return self.conceptManager.get('ispartof') + @Lazy + def queryTargetPredicate(self): + return self.conceptManager.get('querytarget') + @Lazy def memberPredicate(self): return self.conceptManager.get('ismember') diff --git a/browser/concept.py b/browser/concept.py index 86b064b..3e7e6ab 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -282,8 +282,16 @@ class ConceptView(BaseView): def breadcrumbsTitle(self): return self.title + @Lazy + def showInBreadcrumbs(self): + return self.options('show_in_breadcrumbs') + @Lazy def breadcrumbsParent(self): + for p in self.context.getParents([self.defaultPredicate]): + view = self.nodeView.getViewForTarget(p) + if view.showInBreadcrumbs: + return view return None def getData(self, omit=('title', 'description')): diff --git a/browser/node.py b/browser/node.py index 335deb6..c801466 100644 --- a/browser/node.py +++ b/browser/node.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -109,7 +109,7 @@ class NodeView(BaseView): return [] menu = self.menu data = [dict(label=menu.title, url=menu.url)] - menuItem = self.nearestMenuItem + menuItem = self.getNearestMenuItem(all=True) if menuItem != menu.context: data.append(dict(label=menuItem.title, url=absoluteURL(menuItem, self.request))) @@ -400,10 +400,13 @@ class NodeView(BaseView): @Lazy def nearestMenuItem(self): + return self.getNearestMenuItem() + + def getNearestMenuItem(self, all=False): menu = self.menuObject menuItem = None for p in [self.context] + self.parents: - if not p.isMenuItem(): + if not all and not p.isMenuItem(): menuItem = None elif menuItem is None: menuItem = p From 3bbda09f84f06721a0894edf7b7e0f45e6649025 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 24 Mar 2015 20:05:27 +0100 Subject: [PATCH 082/269] improvements of column-based sorting --- browser/action.py | 2 ++ browser/common.py | 3 ++- expert/field.py | 11 ++++++++++- organize/work/browser.py | 7 +++++++ organize/work/report.py | 6 +++--- organize/work/work_macros.pt | 3 +++ 6 files changed, 27 insertions(+), 5 deletions(-) diff --git a/browser/action.py b/browser/action.py index 06600c5..87514bb 100644 --- a/browser/action.py +++ b/browser/action.py @@ -92,6 +92,8 @@ class DialogAction(Action): urlParams['fixed_type'] = 'yes' if self.viewTitle: urlParams['view_title'] = self.viewTitle + for k, v in self.page.sortInfo.items(): + urlParams['sortinfo_' + k] = v['fparam'] urlParams.update(self.addParams) if self.target is not None: url = self.page.getUrlForTarget(self.target) diff --git a/browser/common.py b/browser/common.py index 850fae1..edd3ec7 100644 --- a/browser/common.py +++ b/browser/common.py @@ -145,7 +145,8 @@ class SortableMixin(object): else: fn = v dir = 'asc' - result[tableName] = dict(colName=fn, ascending=(dir=='asc')) + result[tableName] = dict( + colName=fn, ascending=(dir=='asc'), fparam=v) return result def isSortableColumn(self, tableName, colName): diff --git a/expert/field.py b/expert/field.py index a7fb33d..c43ad8d 100644 --- a/expert/field.py +++ b/expert/field.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -46,6 +46,15 @@ class Field(BaseField): return self.getValue(row) +class StringField(Field): + + def getSelectValue(self, row): + return self.getValue(row).strip() + + def getSortValue(self, row): + return self.getValue(row).strip() + + class TextField(Field): format = 'text/restructured' diff --git a/organize/work/browser.py b/organize/work/browser.py index 478dab5..41afb8e 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -22,6 +22,7 @@ View class(es) for work items. from datetime import date import time +from urllib import urlencode from zope import component from zope.app.security.interfaces import IAuthentication, PrincipalLookupError from zope.app.pagetemplate import ViewPageTemplateFile @@ -657,6 +658,12 @@ class CreateWorkItem(EditObject, BaseTrackView): #notify(ObjectModifiedEvent(obj)) url = self.view.virtualTargetUrl #url = self.request.URL + # append sortinfo parameters: + urlParams = {} + for k, v in self.view.sortInfo.items(): + urlParams['sortinfo_' + k] = v['fparam'] + if urlParams: + url = '%s?%s' % (url, urlencode(urlParams)) self.request.response.redirect(url) return False diff --git a/organize/work/report.py b/organize/work/report.py index d80a4e7..2acbf39 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -37,7 +37,7 @@ from loops.common import adapted, baseObject from loops.expert.browser.export import ResultsConceptCSVExport from loops.expert.browser.report import ReportConceptView from loops.expert.field import Field, TargetField, DateField, StateField, \ - TextField, HtmlTextField, UrlField + StringField, TextField, HtmlTextField, UrlField from loops.expert.field import SubReport, SubReportField from loops.expert.field import TrackDateField, TrackTimeField from loops.expert.field import WorkItemStateField @@ -152,10 +152,10 @@ party = TargetField('userName', u'Party', description=u'The party (usually a person) who did the work.', fieldType='selection', executionSteps=['query', 'sort', 'output']) -workTitle = Field('title', u'Title', +workTitle = StringField('title', u'Title', description=u'The short description of the work.', executionSteps=['sort', 'output']) -workDescription = Field('description', u'Description', +workDescription = StringField('description', u'Description', description=u'The long description of the work.', executionSteps=['output']) duration = DurationField('duration', u'Duration', diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index da449f1..8290af4 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -74,6 +74,9 @@ +

+

Add Work Item
From 9c0922276242e5e00e3b1f7daf3d2b4ab927684e Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 25 Mar 2015 08:01:13 +0100 Subject: [PATCH 083/269] strip leading spaces from title for sort criteria --- expert/field.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/expert/field.py b/expert/field.py index c43ad8d..bf25b7d 100644 --- a/expert/field.py +++ b/expert/field.py @@ -249,7 +249,8 @@ class TargetField(RelationField): if value is not None: value = util.getObjectForUid(value) if value is not None: - return value.title + if value.title is not None: + return value.title.split() def getValue(self, row): value = self.getRawValue(row) From a3e0cac006aa339c3cec5710ca6783b4377676cd Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 25 Mar 2015 15:09:52 +0100 Subject: [PATCH 084/269] store sort infos (by task/concept) in user's favorite data --- browser/action.py | 4 +-- browser/common.py | 4 +++ organize/personal/README.txt | 2 +- organize/personal/favorite.py | 57 +++++++++++++++++++++++++++-------- organize/work/browser.py | 10 +++--- organize/work/work_macros.pt | 5 ++- 6 files changed, 58 insertions(+), 24 deletions(-) diff --git a/browser/action.py b/browser/action.py index 87514bb..56d24da 100644 --- a/browser/action.py +++ b/browser/action.py @@ -92,8 +92,8 @@ class DialogAction(Action): urlParams['fixed_type'] = 'yes' if self.viewTitle: urlParams['view_title'] = self.viewTitle - for k, v in self.page.sortInfo.items(): - urlParams['sortinfo_' + k] = v['fparam'] + #for k, v in self.page.sortInfo.items(): + # urlParams['sortinfo_' + k] = v['fparam'] urlParams.update(self.addParams) if self.target is not None: url = self.page.getUrlForTarget(self.target) diff --git a/browser/common.py b/browser/common.py index edd3ec7..e2a5ee3 100644 --- a/browser/common.py +++ b/browser/common.py @@ -67,6 +67,8 @@ from loops.common import adapted, baseObject from loops.config.base import DummyOptions from loops.i18n.browser import I18NView from loops.interfaces import IResource, IView, INode, ITypeConcept +from loops.organize.personal import favorite +from loops.organize.party import getPersonForUser from loops.organize.tracking import access from loops.organize.util import getRolesForPrincipal from loops.resource import Resource @@ -147,6 +149,8 @@ class SortableMixin(object): dir = 'asc' result[tableName] = dict( colName=fn, ascending=(dir=='asc'), fparam=v) + result = favorite.update(getPersonForUser( + self.context, self.request), self.target, 'sort', result) return result def isSortableColumn(self, tableName, colName): diff --git a/organize/personal/README.txt b/organize/personal/README.txt index ed19349..d058355 100644 --- a/organize/personal/README.txt +++ b/organize/personal/README.txt @@ -75,7 +75,7 @@ So we are now ready to query the favorites. >>> favs = list(favorites.query(userName=johnCId)) >>> favs - [] + [] >>> list(favAdapted.list(johnC)) ['27'] diff --git a/organize/personal/favorite.py b/organize/personal/favorite.py index 96a8b16..a2e99d8 100644 --- a/organize/personal/favorite.py +++ b/organize/personal/favorite.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2010 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -18,8 +18,6 @@ """ Base classes for a notification framework. - -$Id$ """ from zope.component import adapts @@ -39,17 +37,20 @@ class Favorites(object): def __init__(self, context): self.context = context - def list(self, person, sortKey=None): - for item in self.listTracks(person, sortKey): + def list(self, person, sortKey=None, type='favorite'): + for item in self.listTracks(person, sortKey, type): yield item.taskId - def listTracks(self, person, sortKey=None): + def listTracks(self, person, sortKey=None, type='favorite'): if person is None: return personUid = util.getUidForObject(person) if sortKey is None: sortKey = lambda x: -x.timeStamp for item in sorted(self.context.query(userName=personUid), key=sortKey): + if type is not None: + if item.type != type: + continue yield item def add(self, obj, person, data=None): @@ -57,21 +58,23 @@ class Favorites(object): return False uid = util.getUidForObject(obj) personUid = util.getUidForObject(person) - if self.context.query(userName=personUid, taskId=uid): - return False if data is None: - data = {} + data = {'type': 'favorite'} + for track in self.context.query(userName=personUid, taskId=uid): + if track.type == data['type']: # already present + return False return self.context.saveUserTrack(uid, 0, personUid, data) - def remove(self, obj, person): + def remove(self, obj, person, type='favorite'): if None in (obj, person): return False uid = util.getUidForObject(obj) personUid = util.getUidForObject(person) changed = False - for t in self.context.query(userName=personUid, taskId=uid): - changed = True - self.context.removeTrack(t) + for track in self.context.query(userName=personUid, taskId=uid): + if track.type == type: + changed = True + self.context.removeTrack(track) return changed @@ -81,3 +84,31 @@ class Favorite(Track): typeName = 'Favorite' + @property + def type(self): + return self.data.get('type') or 'favorite' + + +def update(person, task, type, data): + if person is not None: + favorites = task.getLoopsRoot().getRecordManager().get('favorites') + if favorites is None: + return data + personUid = util.getUidForObject(person) + taskUid = util.getUidForObject(task) + for fav in favorites.query(userName=personUid, taskId=taskUid): + if fav.data['type'] == 'sort': + fdata = fav.data['sortInfo'] + if not data: + data = fdata + else: + if data != fdata: + newData = fav.data + newData['sortInfo'] = data + fav.data = newData + break + else: + if data: + Favorites(favorites).add(task, person, + dict(type='sort', sortInfo=data)) + return data diff --git a/organize/work/browser.py b/organize/work/browser.py index 41afb8e..9bc303d 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -659,11 +659,11 @@ class CreateWorkItem(EditObject, BaseTrackView): url = self.view.virtualTargetUrl #url = self.request.URL # append sortinfo parameters: - urlParams = {} - for k, v in self.view.sortInfo.items(): - urlParams['sortinfo_' + k] = v['fparam'] - if urlParams: - url = '%s?%s' % (url, urlencode(urlParams)) + #urlParams = {} + #for k, v in self.view.sortInfo.items(): + # urlParams['sortinfo_' + k] = v['fparam'] + #if urlParams: + # url = '%s?%s' % (url, urlencode(urlParams)) self.request.response.redirect(url) return False diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 8290af4..5e04470 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -74,9 +74,8 @@ -

- +

Add Work Item
From 335217081c87c0976f4bbdd6f9123301f0dd7e40 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 25 Mar 2015 17:39:51 +0100 Subject: [PATCH 085/269] base functionality for storing selected institution of user/person in favorites --- browser/common.py | 4 ++-- organize/personal/favorite.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/browser/common.py b/browser/common.py index e2a5ee3..5936abf 100644 --- a/browser/common.py +++ b/browser/common.py @@ -149,8 +149,8 @@ class SortableMixin(object): dir = 'asc' result[tableName] = dict( colName=fn, ascending=(dir=='asc'), fparam=v) - result = favorite.update(getPersonForUser( - self.context, self.request), self.target, 'sort', result) + result = favorite.updateSortInfo(getPersonForUser( + self.context, self.request), self.target, result) return result def isSortableColumn(self, tableName, colName): diff --git a/organize/personal/favorite.py b/organize/personal/favorite.py index a2e99d8..59cb665 100644 --- a/organize/personal/favorite.py +++ b/organize/personal/favorite.py @@ -89,7 +89,7 @@ class Favorite(Track): return self.data.get('type') or 'favorite' -def update(person, task, type, data): +def updateSortInfo(person, task, data): if person is not None: favorites = task.getLoopsRoot().getRecordManager().get('favorites') if favorites is None: @@ -112,3 +112,18 @@ def update(person, task, type, data): Favorites(favorites).add(task, person, dict(type='sort', sortInfo=data)) return data + + +def setInstitution(person, inst): + if person is not None: + favorites = inst.getLoopsRoot().getRecordManager().get('favorites') + if favorites is None: + return + personUid = util.getUidForObject(person) + taskUid = util.getUidForObject(inst) + for fav in favorites.query(userName=personUid): + if fav.type == 'institution': + fav.taskId = taskUid + favorites.indexTrack(None, fav, 'taskId') + else: + Favorites(favorites).add(inst, person, dict(type='institution')) From e70bbb941b54d970023dcc5a7747ef24ceafa84b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 28 Mar 2015 09:48:42 +0100 Subject: [PATCH 086/269] move institution selection macro from cyberapps.knowledge to more central place in loops --- knowledge/knowledge_macros.pt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/knowledge/knowledge_macros.pt b/knowledge/knowledge_macros.pt index f775b71..b4f3d2a 100644 --- a/knowledge/knowledge_macros.pt +++ b/knowledge/knowledge_macros.pt @@ -1,6 +1,27 @@ + +
+
+ Organisation/Team: + + + +
+ +
+ +
From 0bac3ecc134b44b693bf1db7ff0baf7a4614698c Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 29 Mar 2015 15:23:56 +0200 Subject: [PATCH 087/269] do not show ugly lines below minor headlines --- browser/loops.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/browser/loops.css b/browser/loops.css index d6b9acc..eabe375 100644 --- a/browser/loops.css +++ b/browser/loops.css @@ -238,18 +238,21 @@ fieldset.box td { font-weight: bold; color: #444; padding-top: 0.4em; + border-bottom: none; } .content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 { font-size: 130%; font-weight: normal; padding-top: 0.3em; + border-bottom: none; } .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4, h5 { font-size: 120%; /* border: none; */ padding-top: 0.2em; + border-bottom: none; } .box { From c06785b98b1f0123104cbcb219954b0f777b9b12 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 30 Mar 2015 09:10:13 +0200 Subject: [PATCH 088/269] avoid error when no task is assigned --- organize/work/work_macros.pt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 5e04470..7071d7c 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -282,7 +282,7 @@
Deadline: -
+ From c49f87aef51149fe77b26c01191da9044b411c4d Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 12 Apr 2015 09:01:22 +0200 Subject: [PATCH 091/269] remove permission restriction from configure.zcml - is checked in form class --- knowledge/glossary/configure.zcml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/knowledge/glossary/configure.zcml b/knowledge/glossary/configure.zcml index 6919ed3..a8924c8 100755 --- a/knowledge/glossary/configure.zcml +++ b/knowledge/glossary/configure.zcml @@ -1,5 +1,3 @@ - - From 2567347f625a3d0cf46f42dc76e6289194ab5418 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 13 Apr 2015 10:49:46 +0200 Subject: [PATCH 092/269] add utility method for getting current date e.g. for input fields --- browser/common.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/browser/common.py b/browser/common.py index dcfaf13..d26966f 100644 --- a/browser/common.py +++ b/browser/common.py @@ -22,7 +22,7 @@ Common base class for loops browser view classes. from cgi import parse_qs, parse_qsl #import mimetypes # use more specific assignments from cybertools.text -from datetime import datetime +from datetime import date, datetime import re from time import strptime from urllib import urlencode @@ -61,6 +61,7 @@ from cybertools.stateful.interfaces import IStateful from cybertools.text import mimetypes from cybertools.typology.interfaces import IType, ITypeManager from cybertools.util.date import toLocalTime +from cybertools.util.format import formatDate from cybertools.util.jeep import Jeep from loops.browser.util import normalizeForUrl from loops.common import adapted, baseObject @@ -206,6 +207,10 @@ class BaseView(GenericView, I18NView, SortableMixin): pass saveRequest(request) + def todayFormatted(self): + return formatDate(date.today(), 'date', 'short', + self.languageInfo.language) + def checkPermissions(self): return canAccessObject(self.context) From 6a96d72b22e46f3fc5b43e437b8c21f426041ac5 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 14 Apr 2015 16:23:47 +0200 Subject: [PATCH 093/269] allow specification of default resource type via global option --- browser/form.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/browser/form.py b/browser/form.py index 4482df4..e2a6c65 100755 --- a/browser/form.py +++ b/browser/form.py @@ -325,8 +325,11 @@ class CreateObjectForm(ObjectForm): @Lazy def defaultTypeToken(self): - return (self.controller.params.get('form.create.defaultTypeToken') - or '.loops/concepts/textdocument') + setting = self.controller.params.get('form.create.defaultTypeToken') + if setting: + return setting + opt = self.globalOptions('form.create.default_type_token') + return opt and opt[0] or '.loops/concepts/textdocument' @Lazy def typeToken(self): From 7ba0bec7f7cae437a300758ca6ea56a80d72cd7b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 16 Apr 2015 08:02:38 +0200 Subject: [PATCH 094/269] allow overriding of version view class by controller --- browser/concept_macros.pt | 3 ++- versioning/browser.py | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/browser/concept_macros.pt b/browser/concept_macros.pt index ca396ed..07620c9 100644 --- a/browser/concept_macros.pt +++ b/browser/concept_macros.pt @@ -370,7 +370,8 @@ - + Date: Thu, 16 Apr 2015 16:54:35 +0200 Subject: [PATCH 095/269] avoid error because of old favorite tems --- organize/personal/favorite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/organize/personal/favorite.py b/organize/personal/favorite.py index 59cb665..ef97974 100644 --- a/organize/personal/favorite.py +++ b/organize/personal/favorite.py @@ -97,7 +97,7 @@ def updateSortInfo(person, task, data): personUid = util.getUidForObject(person) taskUid = util.getUidForObject(task) for fav in favorites.query(userName=personUid, taskId=taskUid): - if fav.data['type'] == 'sort': + if fav.data.get('type') == 'sort': fdata = fav.data['sortInfo'] if not data: data = fdata From 0e28cefc2dea558d2bf563bdec41d30e94dd25ca Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 17 Apr 2015 06:56:47 +0200 Subject: [PATCH 096/269] use setting on type for getting container for object creation --- type.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/type.py b/type.py index 298e112..9f2e915 100644 --- a/type.py +++ b/type.py @@ -105,6 +105,10 @@ class LoopsType(BaseType): @Lazy def defaultContainer(self): + if self.typeProvider: + cont = ITypeConcept(self.typeProvider).conceptManager + if cont and cont in self.root: + return self.root[cont] return self.root[self.containerMapping.get(self.qualifiers[0], 'concept')] @Lazy From 3470750fc7d3bf40127e9bf5a3cce71f4475c645 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 17 Apr 2015 07:13:54 +0200 Subject: [PATCH 097/269] use separate field for questionnaire header text --- knowledge/survey/interfaces.py | 7 +++++++ knowledge/survey/view_macros.pt | 5 ++++- locales/de/LC_MESSAGES/loops.mo | Bin 26583 -> 26968 bytes locales/de/LC_MESSAGES/loops.po | 14 +++++++++++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index 40f1fed..a6b3300 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -33,6 +33,13 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): """ A collection of questions for setting up a survey. """ + questionnaireHeader = schema.Text( + title=_(u'Questionnaire Header'), + description=_(u'Text that will appear at the top of the questionnaire.'), + default=u'', + missing_value=u'', + required=False) + defaultAnswerRange = schema.Int( title=_(u'Answer Range'), description=_(u'Number of items (answer options) to select from.'), diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index d5adc7d..2c4efed 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -7,7 +7,10 @@ errors item/errors"> - +
diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 396cbca0436a66e515e1d6ea55069230c1947e6b..a83eb06f2efe58fd957bc6ad321fdfe807966c7c 100644 GIT binary patch delta 9953 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 bgsd|y78cAJopv)YYee@<|E$`*%Lo4-_K);q diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 7ec4859..6eb5b10 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.1\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2015-03-22 12:00 CET\n" +"PO-Revision-Date: 2015-04-17 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -198,6 +198,18 @@ msgstr "Keine Gruppierung der Fragen" msgid "The questions should be presented in a linear manner, not grouped by categories or question groups." msgstr "Die Fragen sollen in linearer Reihenfolge ausgegeben und nicht nach Fragengruppen bzw. Kategorien gruppiert werden." +msgid "Questionnaire Header" +msgstr "Infotext zum Fragebogen" + +msgid "Text that will appear at the top of the questionnaire." +msgstr "Text, der vor dem Fragebogen erscheinen soll" + +msgid "Feedback Header" +msgstr "Infotext zur Auswertung" + +msgid "Text that will appear at the top of the feedback page." +msgstr "Text, der oben auf der Auswertungsseite erscheinen soll." + msgid "Feedback Footer" msgstr "Auswertungs-Hinweis" From 58b4db66cc44672b576f443286a52c0d3ffadb9d Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 17 Apr 2015 09:14:33 +0200 Subject: [PATCH 098/269] work in progress: handling of question types --- knowledge/survey/browser.py | 16 +++++++++-- knowledge/survey/interfaces.py | 12 +++++++- knowledge/survey/view_macros.pt | 47 +++++++++++++++++++++++--------- locales/de/LC_MESSAGES/loops.mo | Bin 26968 -> 27151 bytes locales/de/LC_MESSAGES/loops.po | 9 ++++++ 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 8d6207e..502429b 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -47,6 +47,8 @@ class SurveyView(ConceptView): batchSize = 12 teamData = None + template = template + @Lazy def macro(self): self.registerDojo() @@ -137,6 +139,8 @@ class SurveyView(ConceptView): if data: resp = Response(self.adapted, None) for qu in self.adapted.questions: + if qu.questionType != 'value_selection': + continue if qu.uid in data: resp.values[qu] = data[qu.uid] qgAvailable = True @@ -231,13 +235,16 @@ class SurveyView(ConceptView): text = u'%s
(%s)' % (text, info) return text - def getValues(self, question): - setting = None + def loadData(self): if self.data is None: respManager = Responses(self.context) respManager.personId = (self.request.form.get('person') or respManager.getPersonId()) self.data = respManager.load() + + def getValues(self, question): + setting = None + self.loadData() if self.data: setting = self.data.get(question.uid) if setting is None: @@ -250,6 +257,11 @@ class SurveyView(ConceptView): title=opt['description'])) return result + def getTextValue(self, question): + self.loadData() + if self.data: + return self.data.get(question.uid) + def getCssClass(self, question): cls = '' if self.errors and self.data.get(question.uid) is None: diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index a6b3300..f6d0b5d 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -26,7 +26,7 @@ from zope import interface, component, schema from cybertools.composer.schema.grid.interfaces import Records from cybertools.knowledge.survey import interfaces from loops.interfaces import IConceptSchema, ILoopsAdapter -from loops.util import _ +from loops.util import _, KeywordVocabulary class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): @@ -108,6 +108,16 @@ class IQuestion(IConceptSchema, interfaces.IQuestion): """ A single question within a questionnaire. """ + questionType = schema.Choice( + title=_(u'Question Type'), + description=_(u'Select the type of the question.'), + source=KeywordVocabulary(( + ('value_selection', _(u'Value Selection')), + ('text', _(u'Text')), + )), + default='value_selection', + required=True) + required = schema.Bool( title=_(u'Required'), description=_(u'Question must be answered.'), diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 2c4efed..29454a9 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -93,19 +93,10 @@ tal:content="opt/label|string:" />
- - - + + +
 
- - -
@@ -119,4 +110,34 @@ + + + + + + + + + + + + + +
+ + + + + + + diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index a83eb06f2efe58fd957bc6ad321fdfe807966c7c..3166e4b00e97baae77b080aee268cd5e2d5f9f8e 100644 GIT binary patch delta 9842 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 H?Hc$W%k?I? delta 9643 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 diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 6eb5b10..7ded724 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -225,6 +225,15 @@ msgstr "Mindestanzahl an Antworten" msgid "Minumum number of questions that have to be answered. Empty means all questions have to be answered." msgstr "Anzahl der Fragen, die mindestens beantwortet werden müssen. Keine Angabe: Es müssen alle Fragen beantwortet werden." +msgid "Question Type" +msgstr "Fragentyp" + +msgid "Select the type of the question." +msgstr "Bitte den Typ der Frage auswählen." + +msgid "Value Selection" +msgstr "Auswahl Bewertung" + msgid "Required" msgstr "Pflichtfrage" From 05b13e154f776c2a14ce616821738c6944216fc5 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 17 Apr 2015 10:50:21 +0200 Subject: [PATCH 099/269] fix check for show_in_breadcrumbs option --- browser/concept.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser/concept.py b/browser/concept.py index 1b1003b..0af9b28 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -284,7 +284,8 @@ class ConceptView(BaseView): @Lazy def showInBreadcrumbs(self): - return self.options('show_in_breadcrumbs') + return (self.options('show_in_breadcrumbs') or + self.typeOptions('show_in_breadcrumbs')) @Lazy def breadcrumbsParent(self): From 67fe55056f228a949be3bb060edd1993f49df89a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 17 Apr 2015 11:17:37 +0200 Subject: [PATCH 100/269] show resources (other than text) in book_topic_view --- compound/book/browser.py | 5 ++++- compound/book/view_macros.pt | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/compound/book/browser.py b/compound/book/browser.py index fb6fcf6..fb43ab2 100644 --- a/compound/book/browser.py +++ b/compound/book/browser.py @@ -107,6 +107,7 @@ class Base(object): @Lazy def textResources(self): self.images = [[]] + self.otherResources = [] result = [] idx = 0 for rv in self.getResources(): @@ -115,7 +116,7 @@ class Base(object): idx += 1 result.append(rv) self.images.append([]) - else: + elif rv.context.contentType.startswith('image/'): self.registerDojoLightbox() url = self.nodeView.getUrlForTarget(rv.context) src = '%s/mediaasset.html?v=small' % url @@ -123,6 +124,8 @@ class Base(object): img = dict(src=src, fullImageUrl=fullSrc, title=rv.title, description=rv.description, url=url, object=rv) self.images[idx].append(img) + else: + self.otherResources.append(rv) return result def getDocumentTypeForResource(self, r): diff --git a/compound/book/view_macros.pt b/compound/book/view_macros.pt index c85a69c..1fcf7b1 100644 --- a/compound/book/view_macros.pt +++ b/compound/book/view_macros.pt @@ -130,7 +130,8 @@ + textResources textResources|item/textResources; + resources item/otherResources">

Children

@@ -151,8 +152,18 @@
more...

+
+ + +
+ From 0936181bf67a029c08a7e9a625edb95047734c80 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 18 Apr 2015 09:33:21 +0200 Subject: [PATCH 101/269] avoid error because of missing description field --- knowledge/survey/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 502429b..864f62f 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -254,7 +254,7 @@ class SurveyView(ConceptView): for opt in self.answerOptions: value = str(opt['value']) result.append(dict(value=value, checked=(setting == value), - title=opt['description'])) + title=opt.get('description') or u'')) return result def getTextValue(self, question): From 7a0ddb1e0d84c32d56f6d94c968e423a18536c7b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 18 Apr 2015 09:41:25 +0200 Subject: [PATCH 102/269] show column description also in tooltip of header --- knowledge/survey/view_macros.pt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 29454a9..ed4c202 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -90,6 +90,8 @@ From c3b9e1b665b6246a6d4ba43ee6125fd04ff3fefc Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 18 Apr 2015 09:42:40 +0200 Subject: [PATCH 103/269] show column description also in tooltip of header --- knowledge/survey/view_macros.pt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index ed4c202..8055847 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -91,7 +91,7 @@ style="text-align: center" i18n:translate="" i18n:attributes="title" - tal:attributes="title opt/description" + tal:attributes="title opt/description|string:" tal:content="opt/label|string:" /> From 8d66ee38301e221c433e4ec65b8aef03df57ece2 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 18 Apr 2015 13:43:44 +0200 Subject: [PATCH 104/269] allow blocking of security acquisition by option; try to keep current role permission setting on object if any --- security/common.py | 8 ++++++-- security/setter.py | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/security/common.py b/security/common.py index f87317a..064ea00 100644 --- a/security/common.py +++ b/security/common.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -71,6 +71,8 @@ def getOption(obj, option, checkType=True): opts = component.queryAdapter(adapted(obj), IOptions) if opts is not None: opt = opts(option, None) + if opt is True: + return opt if opt: return opt[0] if not checkType: @@ -79,7 +81,9 @@ def getOption(obj, option, checkType=True): if typeMethod is not None: opts = component.queryAdapter(adapted(typeMethod()), IOptions) if opts is not None: - opt = opts(option, [None]) + opt = opts(option, None) + if opt is True: + return opt if opt: return opt[0] return None diff --git a/security/setter.py b/security/setter.py index 135db0e..6138269 100644 --- a/security/setter.py +++ b/security/setter.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -39,6 +39,7 @@ from loops.interfaces import IConceptSchema, IBaseResourceSchema, ILoopsAdapter from loops.organize.util import getPrincipalFolder, getGroupsFolder, getGroupId from loops.security.common import overrides, setRolePermission, setPrincipalRole from loops.security.common import allRolesExceptOwner, acquiringPredicateNames +from loops.security.common import getOption from loops.security.interfaces import ISecuritySetter from loops.versioning.interfaces import IVersionable @@ -55,10 +56,18 @@ class BaseSecuritySetter(object): def baseObject(self): return baseObject(self.context) + @Lazy + def adapted(self): + return adapted(self.context) + @Lazy def conceptManager(self): return self.baseObject.getLoopsRoot().getConceptManager() + @Lazy + def options(self): + return IOptions(self.adapted) + @Lazy def typeOptions(self): type = self.baseObject.getType() @@ -133,9 +142,14 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter): def acquireRolePermissions(self): settings = {} + rpm = self.rolePermissionManager + for p, r, s in rpm.getRolesAndPermissions(): + settings[(p, r)] = s for p in self.parents: if p == self.baseObject: continue + if getOption(p, 'security.no_propagate', checkType=False): + continue secProvider = p wi = p.workspaceInformation if wi: From 2c548a3df6c6bffb97909d21a207cfaa71a2c325 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 18 Apr 2015 15:40:43 +0200 Subject: [PATCH 105/269] revert some changes; provide optional logging of acquired security settings --- security/setter.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/security/setter.py b/security/setter.py index 6138269..d185b98 100644 --- a/security/setter.py +++ b/security/setter.py @@ -21,6 +21,7 @@ Base classes for security setters, i.e. adapters that provide standardized methods for setting role permissions and other security-related stuff. """ +from logging import getLogger from zope.app.security.settings import Allow, Deny, Unset from zope.app.securitypolicy.interfaces import \ IRolePermissionMap, IRolePermissionManager, \ @@ -43,6 +44,8 @@ from loops.security.common import getOption from loops.security.interfaces import ISecuritySetter from loops.versioning.interfaces import IVersionable +logger = getLogger('loops.security') + class BaseSecuritySetter(object): @@ -142,16 +145,16 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter): def acquireRolePermissions(self): settings = {} - rpm = self.rolePermissionManager - for p, r, s in rpm.getRolesAndPermissions(): - settings[(p, r)] = s - for p in self.parents: - if p == self.baseObject: + #rpm = IRolePermissionMap(self.baseObject) + #for p, r, s in rpm.getRolesAndPermissions(): + # settings[(p, r)] = s + for parent in self.parents: + if parent == self.baseObject: continue - if getOption(p, 'security.no_propagate', checkType=False): + if getOption(parent, 'security.no_propagate', checkType=False): continue - secProvider = p - wi = p.workspaceInformation + secProvider = parent + wi = parent.workspaceInformation if wi: if wi.propagateRolePermissions == 'none': continue @@ -161,6 +164,10 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter): for p, r, s in rpm.getRolesAndPermissions(): current = settings.get((p, r)) if current is None or overrides(s, current): + if self.globalOptions('security.log_acquired_setting'): + logger.info('*** %s: %s, %s: current %s; new from %s: %s' % + (self.baseObject.__name__, p, r, current, + parent.__name__, s)) settings[(p, r)] = s self.setDefaultRolePermissions() self.setRolePermissions(settings) From 09b75367a72b9931ceecc9a92f01dfcd76c1ad23 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 19 Apr 2015 09:28:30 +0200 Subject: [PATCH 106/269] more control on propagation of role permissions via option --- security/setter.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/security/setter.py b/security/setter.py index d185b98..3f539d5 100644 --- a/security/setter.py +++ b/security/setter.py @@ -151,7 +151,8 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter): for parent in self.parents: if parent == self.baseObject: continue - if getOption(parent, 'security.no_propagate', checkType=False): + if getOption(parent, 'security.no_propagate_rolepermissions', + checkType=False): continue secProvider = parent wi = parent.workspaceInformation @@ -234,14 +235,20 @@ class ConceptSecuritySetter(LoopsObjectSecuritySetter): adapts(IConceptSchema) + @Lazy + def noPropagateRolePermissions(self): + return getOption(self.baseObject, 'security.no_propagate_rolepermissions', + checkType=False) + def setAcquiredSecurity(self, relation, revert=False, updated=None): if updated and relation.second in updated: return if relation.predicate not in self.acquiringPredicates: return setter = ISecuritySetter(adapted(relation.second)) - setter.setDefaultRolePermissions() - setter.acquireRolePermissions() + if not self.noPropagateRolePermissions: + setter.setDefaultRolePermissions() + setter.acquireRolePermissions() setter.acquirePrincipalRoles() #wi = baseObject(self.context).workspaceInformation #if wi and not wi.propagateParentSecurity: From adbdb2840bc2576666d7cc922ca76871ac9130e9 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 19 Apr 2015 15:16:49 +0200 Subject: [PATCH 107/269] provide institution selection for team-based surveys --- knowledge/browser.py | 61 ++++++++++++++++++++++++++++++++- knowledge/survey/browser.py | 17 ++++++--- knowledge/survey/interfaces.py | 32 ++++++++++++----- knowledge/survey/view_macros.pt | 6 +++- 4 files changed, 101 insertions(+), 15 deletions(-) diff --git a/knowledge/browser.py b/knowledge/browser.py index 6126661..459863c 100644 --- a/knowledge/browser.py +++ b/knowledge/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -30,8 +30,13 @@ from cybertools.typology.interfaces import IType from loops.browser.action import DialogAction from loops.browser.common import BaseView from loops.browser.concept import ConceptView +from loops.common import adapted from loops.knowledge.interfaces import IPerson, ITask from loops.organize.party import getPersonForUser +from loops.organize.personal import favorite +from loops.organize.personal.interfaces import IFavorites +from loops.security.common import checkPermission +from loops import util from loops.util import _ @@ -69,6 +74,60 @@ actions.register('createQualification', 'portlet', DialogAction, ) +class InstitutionMixin(object): + + knowledge_macros = knowledge_macros + + @Lazy + def institutionType(self): + return self.conceptManager['institution'] + + @Lazy + def institutions(self): + if checkPermission('loops.ManageWorkspaces', self.context): + return self.getAllInstitutions() + result = [] + p = getPersonForUser(self.context, self.request) + if p is None: + return result + for parent in p.getParents( + [self.memberPredicate, self.masterPredicate]): + if parent.conceptType == self.institutionType: + result.append(dict( + object=adapted(parent), + title=parent.title, + uid=util.getUidForObject(parent))) + return result + + def getAllInstitutions(self): + insts = self.institutionType.getChildren([self.typePredicate]) + return [dict(object=adapted(inst), + title=inst.title, + uid=util.getUidForObject(inst)) for inst in insts] + + def setInstitution(self, uid): + inst = util.getObjectForUid(uid) + person = getPersonForUser(self.context, self.request) + favorite.setInstitution(person, inst) + self.institution = inst + return True + + def getSavedInstitution(self): + person = getPersonForUser(self.context, self.request) + favorites = IFavorites(self.loopsRoot.getRecordManager()['favorites']) + for inst in favorites.list(person, type='institution'): + return adapted(util.getObjectForUid(inst)) + + @Lazy + def institution(self): + saved = self.getSavedInstitution() + for inst in self.institutions: + if inst['object'] == saved: + return inst['object'] + if self.institutions: + return self.institutions[0]['object'] + + class MyKnowledge(ConceptView): template = template diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 864f62f..09286ef 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -32,6 +32,7 @@ from cybertools.util.date import formatTimeStamp from loops.browser.concept import ConceptView from loops.browser.node import NodeView from loops.common import adapted, baseObject +from loops.knowledge.browser import InstitutionMixin from loops.knowledge.survey.response import Responses from loops.organize.party import getPersonForUser from loops.util import getObjectForUid @@ -40,7 +41,7 @@ from loops.util import _ template = ViewPageTemplateFile('view_macros.pt') -class SurveyView(ConceptView): +class SurveyView(InstitutionMixin, ConceptView): data = None errors = None @@ -69,6 +70,11 @@ class SurveyView(ConceptView): if self.editable: return 'index.html' + def update(self): + instUid = self.request.form.get('select_institution') + if instUid: + return self.setInstitution(instUid) + @Lazy def groups(self): result = [] @@ -129,11 +135,12 @@ class SurveyView(ConceptView): pred = self.conceptManager.get('ismember') if pred is None: return result - personId = respManager.personId - person = self.getObjectForUid(personId) - inst = person.getParents([pred]) + #personId = respManager.personId + #person = self.getObjectForUid(personId) + #inst = person.getParents([pred]) + inst = self.institution if inst: - for c in inst[0].getChildren([pred]): + for c in inst.getChildren([pred]): uid = self.getUidForObject(c) data = respManager.load(uid) if data: diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index f6d0b5d..49ab653 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -40,6 +40,16 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): missing_value=u'', required=False) + questionnaireType = schema.Choice( + title=_(u'Questionnaire Type'), + description=_(u'Select the type of the questionnaire.'), + source=KeywordVocabulary(( + ('standard', _(u'Standard Questionnaire')), + ('pref_selection', _(u'Preference Selection')), + )), + default='standard', + required=True) + defaultAnswerRange = schema.Int( title=_(u'Answer Range'), description=_(u'Number of items (answer options) to select from.'), @@ -66,6 +76,12 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): default=False, required=False) + teamBasedEvaluation = schema.Bool( + title=_(u'Team-based Evaluation'), + description=_(u'.'), + default=False, + required=False) + feedbackColumns = Records( title=_(u'Feedback Columns'), description=_(u'Column definitions for the results table ' @@ -109,14 +125,14 @@ class IQuestion(IConceptSchema, interfaces.IQuestion): """ questionType = schema.Choice( - title=_(u'Question Type'), - description=_(u'Select the type of the question.'), - source=KeywordVocabulary(( - ('value_selection', _(u'Value Selection')), - ('text', _(u'Text')), - )), - default='value_selection', - required=True) + title=_(u'Question Type'), + description=_(u'Select the type of the question.'), + source=KeywordVocabulary(( + ('value_selection', _(u'Value Selection')), + ('text', _(u'Text')), + )), + default='value_selection', + required=True) required = schema.Bool( title=_(u'Required'), diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 8055847..3687152 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -4,7 +4,8 @@ + errors item/errors; + dummy item/update">
+ + +

Questionnaire

From 5d4a74c528e0c4a7e512f8faeb5e4f922fe52938 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 20 Apr 2015 10:12:29 +0200 Subject: [PATCH 108/269] allow suppression for listing all institutions for workgroup admin, e.g. for surveys --- knowledge/browser.py | 7 +++++-- knowledge/survey/browser.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/knowledge/browser.py b/knowledge/browser.py index 459863c..8eb8645 100644 --- a/knowledge/browser.py +++ b/knowledge/browser.py @@ -78,14 +78,17 @@ class InstitutionMixin(object): knowledge_macros = knowledge_macros + adminMaySelectAllInstitutions = True + @Lazy def institutionType(self): return self.conceptManager['institution'] @Lazy def institutions(self): - if checkPermission('loops.ManageWorkspaces', self.context): - return self.getAllInstitutions() + if self.adminMaySelectAllInstitutions: + if checkPermission('loops.ManageWorkspaces', self.context): + return self.getAllInstitutions() result = [] p = getPersonForUser(self.context, self.request) if p is None: diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 09286ef..c3a99e8 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -50,6 +50,8 @@ class SurveyView(InstitutionMixin, ConceptView): template = template + adminMaySelectAllInstitutions = False + @Lazy def macro(self): self.registerDojo() From 328c1fbaaf63d03efd76a4d230f6e9bd97552c96 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 20 Apr 2015 10:30:49 +0200 Subject: [PATCH 109/269] provide button for saving data without trying an evaluation --- knowledge/survey/browser.py | 12 ++++++++++-- knowledge/survey/view_macros.pt | 7 +++++++ locales/de/LC_MESSAGES/loops.mo | Bin 27151 -> 27291 bytes locales/de/LC_MESSAGES/loops.po | 8 +++++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index c3a99e8..7f4549c 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -44,7 +44,7 @@ template = ViewPageTemplateFile('view_macros.pt') class SurveyView(InstitutionMixin, ConceptView): data = None - errors = None + errors = message = None batchSize = 12 teamData = None @@ -167,7 +167,12 @@ class SurveyView(InstitutionMixin, ConceptView): def results(self): form = self.request.form - if 'submit' not in form: + action = None + for k in ('submit', 'save'): + if k in form: + action = k + break + if action is None: return [] respManager = Responses(self.context) respManager.personId = (self.request.form.get('person') or @@ -187,6 +192,9 @@ class SurveyView(InstitutionMixin, ConceptView): for v in values: data[self.getUidForObject(v['group'])] = v['score'] respManager.save(data) + if action == 'save': + self.message = u'Your data have been saved.' + return [] self.data = data self.errors = self.check(response) if self.errors: diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 3687152..4985420 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -5,6 +5,7 @@ @@ -72,6 +73,10 @@ tal:content="error/text" />
+
+ diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 3166e4b00e97baae77b080aee268cd5e2d5f9f8e..b401142c3cb981966f1a302cc70512957f3ff072 100644 GIT binary patch delta 9855 zcmYk>33yLu+Q;!@$!3X&SQD}kA|eD4YeX!OSYwU7ArX{P5e>?JYAZ_BK8lvw#=b=9 zpewTnkTOh(=Bf!d)F zsQ#v+2A+@AaUH7NLG);ag(PbDDMsLbu_pRNv&|Tedaxy`<6fu^vrtEnkLqw4s-MkP zK7=(WpFyqoOVm;QggWZKqS=3~pdxRUCQ=L4aWvM#MAU$TQ4^Ykn#dxn-;8R16gAPa zsCG9n9E+?zB*yJ04r^1Nf|^iv4EwJR@~NncOOR)r1E>{UMV;kc^u>p$0UleqW@E=G zOF07lumS3&jYGAMHxp4SPQmgx(8{AcBs$AyQ61%@9-M_0aRF)zS6TgD)C7*8cI*P` zxf@pg3bmkLPy_nKx(lg+6(~oejwk{3yr&yUMUo8k$1$j_$wO`ZLR5#Zp&mSh+L=Ps zmS04z{9C*K0R1U@HF5h5LOmCOnm}`_Pr`Eg{(D))VAPrCU?rS`+L;xo39dtJ?GDtD z>_+WW0cwB~SP4Huy_8>~KF0^BEiXb%)VrzsTtKm$e@&7~+=wunp|&&`^*{=0;B?f4 z2B3Ci7`DL4R=*9^eh+HGN6a%=hw>+=m+~HJ0afBq-+wTP2CQpFp*m`eT492flTZ^# zLv@gjdKU(xR+@{N=ycQsXJG&?N40wcv+x8~#_(qBzs|5ZiFTkH>cI?Dg9)guoQ+!1 zYSh_oL4AgYQ0+gk`fFDI1L~~3n!C>hqF&-K)B;+g7Le4O^VbTyQBl0bsIwY?dLR?E z6Enabkb=F{)dB_?b zLv8JQsEK}r8t4*gB|h=)SK^Pn%}zbkUz|;_G4{YHoPm58&K7KdpI|*K@{k0Rgtl}C zXp1_E&Q|V^n)z_6&qhrs&+gAe?ZiCP*R#UPdohUeaZJGrsGX~k;2vRZ)Pg)QBvnY_ zQ5~gXRUBjuCZe`%8dk@-R=*x~G+V8_54EzRR=$oJ@DA$vA5iU!Pzx;Es(2wDCx}E_ zSqpXc;i#9av6b7QcBmU_##yNL6H)D^B7dJab1@9}q0at1>aD+p+Uh^CI)=A)`)Q4l z`ujhbL^B_MIFBD=o|_0GJC4RJSWLKjhA%~e!CcaeWcIS)`r_$#X2 zW7Lj&w_#j;|FuXoU?}RrMyP?~Py@HMa#t(&K^@gl4O37+^x<;{X1Y5YRfmFCbAPX!NaJR>lo^@J#Y1&npaROypDSA zKI$F#3$-KV+q*mB-=6)~)>orKFI_OIVVs$WdZ0V1T`$yD_QM)D6!kJqMSYg@a1p+V zYS$srooEVbBEwJ%%0cxvIg$O>0C`ks%k!}d&cQhRH)_R2s0W=6?t^})3Hzg7!g{Cy znp=G$s(pXdQ4Pmh_%z1g0u03h4~briPf-tiZr--~?@@2_ebiQaC%G#thiV^)dOjGn zfC$vYVz3$}qb4>O)jkWgpoysGJh>$7mNNru;#;T(KS8bR8`M_*hU&oS=)PQjr~!km z9Ekyx6HrIe&B__5qZ()Rb5L8q0-1=%SxcgsY(%YeD{7#lsF&;{s>6$R|0~o$zo9zv zOLkZ2k9w{qs-FndR}hbCpJL?<)I!H&Ieq{0NVFx(%+;ul)}to26}1DqQ3DpBUedQv z&;1K^G-s{;JgVJQ)N?n@Z&084Ju5%L0LFK`I=Nd}4fQ~n8HJj8Q`Ceyq9&AvI^%Ry z`y9JJ2{oa7tDkKyKuzdn)Q+q~y_DO~qcbibk*Cd%%^Ro(enic@RA=|Qu87)!aMZ+N zQ4>qRvY3c%N;ys^qW&hPdm9Fj?hM)#&fLdX5tc|ItU$7k1 zZ}@5~gL_dEKZt67(!7M~_j}a4@C)h($v4G)xkIqH{}lFLH&Ut4+dLdKfgEd?i+cGM zqdI&Q)ozQG_o60r#OlwPpP`QK7OKDdI1(SDc4TBX_XxInEIEYwPEVo+IBVsL=tKDi z>T9@z>hKZzqF<`pQ6TDm80vmJdSgek8-`Hsjp}a#>K*V*CDFihP+PSWwdI>oD|-j^ z%XP`h_fYNp(%g3=#Eiu->XT5vADO7<^HHDMD^|Y?HPKVcQry2YOolC^HYWf`zE9UV}Q*9T<*pqb7bG8(OR zanuBEV=z8KZDGxxZu^#~6{cfD9EWXiCF-3zhdQELSPs8KE%YJkGp*K(BVv3fgrp+Y zMZMK=s0ZWCL@fRV!wS@ApkB&s)LG`C2AGBFXCdk*cMXQ&e$;>$P!qm{YIhesdhkyY zZJkeV_kr4|9Z5vh4@RB&B-Gi?vHEqWquFnsLG9QT)K1;EazGzy z)Ze<3JtXRIAL`7Hpl1B8c@85ee~FQ({{>3z>Z3Yrf?8Qivn^_(NvLFOD&gwc&!y?oQp6%}*$qQJ8@>y4vy?j5R zp07C2op3N}oJOd3r#*V<`|m}PLPdWZh8s{X)9Uo#&=MJ!H1&2Y0d+=&{f0JTH! zqgHeU)xixbe~0SlXVek>g*uXooTr|zhH4*!YL|eTaHnDHe>;+aRA?(#Vof}X+JTQT z4!=RY6T!pX6}87^l)IxQJR3E!1$KX_mDihZm^-ls?G9iFzV9K?jJ`(A=x5Z~c#W|C zR6>>gQ3C~`-ue(LH#J+KR-A;|;#5?-e%KguP|vSM^|uid(6fc470DNN>THTK0JPq}{!?!`#Tmr+~(2sJU^On2bQs2#129F@mOwHq0z z2S%X2({ZRRoryZrIj9LOM|HRg_1$m8VBCya@!P0(c0Ndxir3r8elQ@#+8_iw^3VNKg(S}4C*Ut zi&|(B1~R_WokTwzBTz5L)2I&Tp;o>Wwe=gYB_2nu;Ad33fNZzJnr1!Jz)es)5|4Vv zQq2LVoz2AJ?|-b_n1XuiXQQ@o4Ql1vQ4=|gnrNZbUqG$&8tVHFAH%L;E7WsyQ9HF9 zwc>TC9ovf9$sJ?ZfBk?Qq@pvPwuU}A?(ab$>NDwx#XE%R=xOx9Y36LycfJ6%BO6c? z+J@eE0`**>-9KmL%Q@`7R(6XDAH0W}(C?@L%Z+s(td3ey1Zw6@u?)6BFHAH$qjoM0 z)!z_v6zT}ZV_BSrn$S!Si8@+Jk$i2q6U5)^&RiWAiRm%$%hz= zUgO>VTA=z*Mjd4u>iHaGM?B7RB--k^SPoyoQn-m2LUI$^;uh4UZEQt(C2q&As2#~9 z45Re-2*&;i&0mdQk?&Nl4(?)#&q0Dlw8}$2T}Nu=x3E(?S3kbdZO)7 z?uS{MT2}v@m6w|0dtxe~|BtJFe;kg=nMe2$y8dPlziiIO%7lKFcM!v^Uf;H^gD%B? zhH>`#BhY4NDvHway`X;+o#Mt+0nMShA{MBZ3uFn~hIl|bV2MENbtci_9k z9%4>OV|@9E{5A4**1j>er(7O?vo>$xT*`fk^H$eH@4zl9atK{_iFU+d$}bX4h*p#< zU>Z?!C6egJL{}|hUvb9gWo-uGuhi*t)Ab7FS$I+fuK7eN;rTl^mJ^*QTq8=84<^cy z>pDmLhj@y*21LpAEXmW9Z()qppCwNsS`xaZQ~zIFQ(U6GxsdvPMEg?g|7LqQlAE0^ zUrw%Hs*c@I{fhieVkG5Pa2!s? zPl=Y~8o&68rf`sWmiQ+%4Xl0&Yr6#( zQXWr~BmX=8Kx7jBI{)q@x(*Pn?ID$4)E`_mtS*YYIe8G~;Y^~4yg#-fbUh*_laD2i z66=a<-9Nsy{9Q8z_1_@QQ$%T^hjsFQ#)i9AAA2>0p{S>)A=Yxv*s@C-4L`hVad ztItwD3a(!W{R??3F_NfH^yJ=pB7yLX=8xG_>Kg6h{9#t--Vs9ARTrnMmEWekji_Vw z8>xSuXia$$E+8fly2`jX@fb@~AU1I?Tqiw=!rRvH82OvzX}AV!65WV6$|F!$GZ!Zv zds|%$d1vAUq7gBZsK~t)L@xOpOd%$ce@XmBcoXmGP3uD9#m#A`s{>Jwm_c1%?1+Vg zu6%rq?+`o5n-Qys-9*Xtz9r94cY?eR&Li5A7vKdvgiDCxH~l5lHH2tHPBT}KNFTqmK9qN)gjocbhD-fT{<_YUWUd6BZpq_ kt&=)-O7Ww#doFKE7UX2-PR<@TdTjQTsj&sISvS4@2l+<~TmS$7 delta 9740 zcmZYE33!fI-pBDfNJJ#ESRx{lAPExt5=#(kT7=k3tQApPI@S@?)3J34Rg_vv6}2@j zWuml1OIMVk7+O_YbZn!HjK6OMWxYbK)~ z`E=9-ds%)UmSTJ-i$qyo8mhenwh; zDpn*v54GZVQAf24b<|(hV*j;*lN4woKci-P8!Mq7H_m&Os0k&bCeq!?N22O`P!nB@ zs<#Paale&cMD=qIqcAwholxy0_Fo;OQcw+hA$6Q7s1>b8o#jsS$3oNqUs?VFmLPu> zOW{q_rM+kGADMo&-4zF8Y2H_{d@TmIqXv#ZO(+Vr zBh|4HHnsAxr~xLTCOpHOhgHb0KwZk+s0I8W_5J@sLIYkie?xWjJ8GtXSl++BJAp7% z2N9^d5Q|!A3)Dp0peEP>L$DvJ-YCq%*%*pf(4#YaKtekZ(!gyPgR0O7wUr%FD;kJp za5U;OoQfJ~k(F<-@&eRZe~W7OJ?aww1GR%b4c!I!H{|@a!Vn6)TZ}rZC{%-*sGVqQ zc0uh_A5_PgmLG|1l9Pk!_%?>&4dm~Y_nfq2@tl_?RtHtyq7nPAEgDLJDvUR$TE&^D ztzCec=n~XGt5GXCiuy`UA-CDNj{3uS7i(j=#*R}1+aed=8I1|J0;}VG4~a+;zoG^x z-o!nMKvcdmYGQF#UK=%`miE3KYA3p&zMlS;pM>Se&%!iZhT6IFs3W|D>fdvRL|GD# zP#uLPyDN%DRcMUbvQ`+5ovnNr>S)GTelluh9?Ne;4Y&=}z5sQ{_M;YX9N9sSbDD(C z{3q1Ru37#*YG?eKx)Z62+VVtHy#~mi38xLl;26}I&qv+%HK;BA7{l==)PnA!&faOJ z33C4NBs8N$)I{o|R@M@ArfK%R2kNdogEes^YC?-qU&{(qKl#XiKsnn{M^}KVw->e3 zpQCo>G?v!)|04-Cyn-6|CTie&mM_uV&6hzPQ3P^YPJ%heT!Na&Vblc9p)TPC)X@aD zVAn7ihv6vnWRN&QA`+uh+`s=VP+K(!HIWgh364iyswt??b-tA^G~Yz6a22ZEhp4mP zgPPDcs2w?s+JO@(?0YT!Dk{u{Pp|Fz{!DCmHx$d92j7q#MDsD_28hF_s3d>C~Je@3nJmX$w5 z)emd!9#teJk*|r-*avkNCZp=T;IYJ9d$9m@ithNktawaneX=p#G>c8;0s|id(^X88y&KR7d&fykjB)qW@H2={g1{58-)3bevg7=_nSzgz(w-QVt} zSe$$YYQnux^@o|csE!w)?!q$E#NR_*?tSR(-^#C}j=)J{|22VtG`C^})a9#<>aZoM zVn@qop#GTjv+{A~G}PJ7LG`y32jg1Qjud;!J%Ti|H);olc}Qr094nZDCCSf1eGPf2 z4%eVR=A+KC0QLR=>iuc-!;9uMtW5qks=wf;-FD%qf#Xm+<*7$PTiy<}vY}WBb1nY{ zs$xFsZtOFUVGQ|SP+MH0liNNL^|>`fm8YX7nq}{EF`E2Bq(6`IAqfp|0M+mms=;qo z?%UaYUlFzPMATL{L!D_SjKv|SiO<9YT!q!~3)BLyqW;MFcX8`g$105Pv?iejgHRKg zi>kN=wS{|79iKt%&>gIaf$7|OY=XLM;XIT+7Knw<9BI+l%8OC4_)PNIE6V64|TZn4+Hfra#peKgJ zrzEt(3-;n6>PW)6xn~%MYLJ3DnjYo|)Q(L@?bK4s??6rHThs(Dqxyf0xvbxxPNxb zqWbHOI`V!V5@kp{YmURJCkd^jFNWdssI!`hlW`Sl1?76VM^YWjl5dXsth%DkdJL+cIT(toPz&3N zI-){*f70IHK)y1M^9KnH9NF8wrFBp{kb+wAVAPgnqXy1F&3q>6>zIqWe2Y=Hd>d-Q zg{X-f!4N!;TKO$Z!@D@Z`~CNEFVkD7EiXh3coDUN>zIfSumUFZb+@(^Y65AfyV1)W zZ0|=QACfZx)!%;9&K^Wf@Df(l_y0SIMp*F~_dgcXkx$;4iZ$?Stc|y@8b6Ic2Dt;&!({SJuqozX5^h9I#B+j#Ch#lj5?;kB z7|lg!j;(M4=3)=Lfi*F8i2JpSLT&l$sEMsat!zDNN4KDk>MW}K8mj&+8i+C`Z91)S2E#b?86L-O4a5M!phifGF&S@z@q8qgJ*XwbFg4ujdGArQe~p`W)&j zxrMqLkJ0rgwf8Fjh#n%|&y^dxE_XRZ7) z>ayQP?Oecc_FpTmL_#yKftq15s=PgFg*{N;^AhZY`S$)U>azJ}yAupT?Nmk7z>%om zi#TkLO;8J&hIMdmHv6y7qksYpeB3JhgzEUJc^mb8-$(68$Ov~r5$H$09;)MH)cdxU z?~GblA1sN(Q4<=A8gIr3&R-4lDA0Z{tAJhzeM!Ex+#rou{p}vmZsI44@I=fs{hnr9X>_i=9 zA!_13peA+=wbge~J5pj4W1u}A?`oEk<1RUii9bK__7n1N5WR^15Z!sFN$ELBj3w3( zi>ys8((e&^Hd9uMm_$@3|25$u{T1#bS`(FgIR9o8>IpKHP9!~*7(;qK=BUt~ZJ2L$ z?~?wWm}dFsum<@y#37;^(UllMc_`73NFxqVHkim|eCJz2&puuhJ?SLYlW+M%ncnFr zPAI{X{=~akJqB|npxN^G_A6{I<7?FkdX8%Q_8hs3+2ccUKtP0XC3L5I1gVX zJ|^9nSWfgL4ib*O|Dq?1N{h%mi+Y|XYLo7df5P3wYSN{Nxx`r_kFsGzM`A7UJ7sIo zAN9nMPA2rU!M_scNzYOm4{Q7@#89h5()-LMzlwO7@FS`am3X6{+PZF)|M|Bvb@a4# zabCmGL;^9N*ht(YmQrsgZXy0}{clp((>kEs`wS+p-|nL41$%#r0$*Y!@rZcI%KPBY zTWLA!YwYJtK?8FGJxB3hLu~#5bhh!H%lMQ;+l@;ze(cKSHFRBE5%r zp0u7n5kGiS?teuo`7ZH_y^kS%i^wNFBtE75ng3ruB|H;doV~nEv4+Pf`=^zMQ}(N+ z2bv;)_`~w4)VWMLi%23JW&LczJ;djfH^Q03Iqm<8B=n3RhFf|H>1Qk*Pr3r}De*G# z5)nn@5e=yGG%EbvBGl>kM==p&3zlt*Gd6R!;KT*(-^gBc$F`jsphtcnGA zhWLok)855t%exdyPbM8eBw4->zEA!WqA}@q_!vLK)\n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -288,12 +288,18 @@ msgstr "Trifft für unser Unternehmen voll und ganz zu" msgid "Evaluate Questionnaire" msgstr "Fragebogen auswerten" +msgid "Save Data" +msgstr "Daten speichern" + msgid "Reset Responses Entered" msgstr "Eingaben zurücksetzen" msgid "Back to Questionnaire" msgstr "Zurück zum Fragebogen" +msgid "Your data have been saved." +msgstr "Ihre Daten wurden gespeichert." + msgid "Please answer at least $minAnswers questions." msgstr "Bitte beantworten Sie mindestens $minAnswers Fragen." From 57aee009b64f409d23914d2bee9f5831ab0b0cd8 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 21 Apr 2015 07:40:49 +0200 Subject: [PATCH 110/269] store response data of team-based surveys under person + institution key --- knowledge/survey/browser.py | 9 ++++++++- knowledge/survey/response.py | 20 ++++++++++++++++---- organize/tracking/browser.py | 10 +++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 7f4549c..4e95d4d 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -141,10 +141,11 @@ class SurveyView(InstitutionMixin, ConceptView): #person = self.getObjectForUid(personId) #inst = person.getParents([pred]) inst = self.institution + instUid = self.getUidForObject(inst) if inst: for c in inst.getChildren([pred]): uid = self.getUidForObject(c) - data = respManager.load(uid) + data = respManager.load(uid, instUid) if data: resp = Response(self.adapted, None) for qu in self.adapted.questions: @@ -177,6 +178,9 @@ class SurveyView(InstitutionMixin, ConceptView): respManager = Responses(self.context) respManager.personId = (self.request.form.get('person') or respManager.getPersonId()) + if self.adapted.teamBasedEvaluation and self.institution: + respManager.institutionId = self.getUidForObject( + baseObject(self.institution)) data = {} response = Response(self.adapted, None) for key, value in form.items(): @@ -257,6 +261,9 @@ class SurveyView(InstitutionMixin, ConceptView): respManager = Responses(self.context) respManager.personId = (self.request.form.get('person') or respManager.getPersonId()) + if self.adapted.teamBasedEvaluation and self.institution: + respManager.institutionId = self.getUidForObject( + baseObject(self.institution)) self.data = respManager.load() def getValues(self, question): diff --git a/knowledge/survey/response.py b/knowledge/survey/response.py index 6f5635d..8ba293c 100644 --- a/knowledge/survey/response.py +++ b/knowledge/survey/response.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -34,20 +34,32 @@ class Responses(BaseRecordManager): implements(IResponses) storageName = 'survey_responses' + personId = None + institutionId = None def __init__(self, context): self.context = context def save(self, data): if self.personId: - self.storage.saveUserTrack(self.uid, 0, self.personId, data, + id = self.personId + if self.institutionId: + id += '.' + self.institutionId + self.storage.saveUserTrack(self.uid, 0, id, data, update=True, overwrite=True) - def load(self, personId=None): + def load(self, personId=None, institutionId=None): if personId is None: personId = self.personId + if institutionId is None: + institutionId = self.institutionId if personId: - tracks = self.storage.getUserTracks(self.uid, 0, personId) + id = personId + if institutionId: + id += '.' + institutionId + tracks = self.storage.getUserTracks(self.uid, 0, id) + if not tracks: # then try without institution + tracks = self.storage.getUserTracks(self.uid, 0, personId) if tracks: return tracks[0].data return {} diff --git a/organize/tracking/browser.py b/organize/tracking/browser.py index 7e2cd7a..5450983 100644 --- a/organize/tracking/browser.py +++ b/organize/tracking/browser.py @@ -66,7 +66,15 @@ class BaseTrackView(TrackView): obj = util.getObjectForUid(uid) if obj is not None: return obj - return uid + result = [] + for id in uid.split('.'): + if id.isdigit(): + obj = util.getObjectForUid(id) + if obj is not None: + result.append(obj.title) + continue + result.append(id) + return ' / '.join(result) @Lazy def authentication(self): From 2ea0689a050578d77a24966f44e7af15ffd65eaf Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 21 Apr 2015 10:06:28 +0200 Subject: [PATCH 111/269] make team reports callable independent of filling a questionnaire --- knowledge/knowledge_macros.pt | 2 +- knowledge/survey/browser.py | 44 +++++++++++-- knowledge/survey/view_macros.pt | 110 ++++++++++++++++++++------------ locales/de/LC_MESSAGES/loops.mo | Bin 27291 -> 27487 bytes locales/de/LC_MESSAGES/loops.po | 11 +++- 5 files changed, 119 insertions(+), 48 deletions(-) diff --git a/knowledge/knowledge_macros.pt b/knowledge/knowledge_macros.pt index b4f3d2a..133eb2a 100644 --- a/knowledge/knowledge_macros.pt +++ b/knowledge/knowledge_macros.pt @@ -3,7 +3,7 @@ -
+
Organisation/Team: @@ -16,48 +19,7 @@
-

Feedback

-
-
- - - - - -
Category -
- - - -
-

- Team Size: -
  -

-
-
+
+
+ Show Report:   + +

+

Questionnaire

@@ -153,4 +125,58 @@ + +

Feedback

+
+ + + + + + +
Category +
+ + + +
+

+ Team Size: +
  +

+
+
+ + + + +

+
+ Not yet implemented +
+
+ + diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index b401142c3cb981966f1a302cc70512957f3ff072..04847d38826974ab19106eeab77bbb886bcbe2a9 100644 GIT binary patch delta 7866 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 diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index d669a25..9bf2918 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.1\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2015-04-20 12:00 CET\n" +"PO-Revision-Date: 2015-04-21 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -285,6 +285,15 @@ msgstr "Trifft eher zu" msgid "survey_value_3" msgstr "Trifft für unser Unternehmen voll und ganz zu" +msgid "label_survey_show_report" +msgstr "Auswertung anzeigen" + +msgid "label_survey_report_standard" +msgstr "Standard-Auswertung" + +msgid "label_survey_report_questions" +msgstr "Einzelfragen-Auswertung" + msgid "Evaluate Questionnaire" msgstr "Fragebogen auswerten" From f935a0efb7b2e017c7b7c492a08e14685a1cd53e Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 21 Apr 2015 12:42:18 +0200 Subject: [PATCH 112/269] single question report basically working --- knowledge/survey/browser.py | 38 +++++++++++++++++++++++------- knowledge/survey/view_macros.pt | 41 ++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index ebed784..1e82432 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -156,22 +156,26 @@ class SurveyView(InstitutionMixin, ConceptView): def getTeamData(self, respManager): result = [] - pred = self.conceptManager.get('ismember') - if pred is None: + pred = [self.conceptManager.get('ismember'), + self.conceptManager.get('ismaster')] + if None in pred: return result inst = self.institution instUid = self.getUidForObject(inst) if inst: - for c in inst.getChildren([pred]): + for c in inst.getChildren(pred): uid = self.getUidForObject(c) data = respManager.load(uid, instUid) if data: resp = Response(self.adapted, None) for qu in self.adapted.questions: - if qu.questionType != 'value_selection': - continue - if qu.uid in data: - resp.values[qu] = data[qu.uid] + if qu.questionType in (None, 'value_selection'): + if qu.uid in data: + value = data[qu.uid] + if isinstance(value, int) or value.isdigit(): + resp.values[qu] = int(value) + else: + resp.texts[qu] = data.get(qu.uid) or u'' qgAvailable = True for qg in self.adapted.questionGroups: if qg.uid in data: @@ -244,12 +248,30 @@ class SurveyView(InstitutionMixin, ConceptView): groups = self.adapted.questionGroups teamValues = response.getTeamResult(groups, self.teamData) for idx, r in enumerate(teamValues): - item = dict(category=r['group'].title, + group = r['group'] + item = dict(category=group.title, average=int(round(r['average'] * 100)), teamRank=r['rank']) + if group.feedbackItems: + wScore = r['average'] * len(group.feedbackItems) - 0.00001 + item['text'] = group.feedbackItems[int(wScore)].text result.append(item) return result + def getTeamResultsForQuestion(self, question): + result = dict(average=0.0, stddev=0.0) + if self.teamData is None: + respManager = Responses(self.context) + self.teamData = self.getTeamData(respManager) + values = [r.values.get(question) for r in self.teamData] + values = [v for v in values if v is not None] + if values: + average = round(float(sum(values)) / len(values), 2) + result['average'] = average + texts = [r.texts.get(question) for r in self.teamData] + result['texts'] = '
'.join([unicode(t) for t in texts if t]) + return result + def check(self, response): errors = [] values = response.values diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 34ada80..1ae3702 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -158,8 +158,7 @@

 

- Back to Questionnaire
@@ -174,7 +173,43 @@

- Not yet implemented + + + + + + + + + + + + + + + + + +
  
Average
+
+
+
+
+

+ Team Size: +
  +

+
From ae789e117b689b003d4da84e425a089764602cf7 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 21 Apr 2015 13:42:48 +0200 Subject: [PATCH 113/269] take reversed polarity into account, normalize to 100% --- knowledge/survey/browser.py | 7 +++++-- locales/de/LC_MESSAGES/loops.mo | Bin 27487 -> 27548 bytes locales/de/LC_MESSAGES/loops.po | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 1e82432..ec580e6 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -266,8 +266,11 @@ class SurveyView(InstitutionMixin, ConceptView): values = [r.values.get(question) for r in self.teamData] values = [v for v in values if v is not None] if values: - average = round(float(sum(values)) / len(values), 2) - result['average'] = average + average = float(sum(values)) / len(values) + if question.revertAnswerOptions: + average = question.answerRange - average - 1 + average = average * 100 / (question.answerRange - 1) + result['average'] = int(round(average)) texts = [r.texts.get(question) for r in self.teamData] result['texts'] = '
'.join([unicode(t) for t in texts if t]) return result diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 04847d38826974ab19106eeab77bbb886bcbe2a9..1e9869ae698151828c41f823f3f42902345f7a44 100644 GIT binary patch delta 9898 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 delta 9813 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 n Date: Tue, 21 Apr 2015 20:27:30 +0200 Subject: [PATCH 114/269] provide column for standard deviation on single question report - but keep inactive at the moment --- knowledge/survey/browser.py | 5 +++++ knowledge/survey/view_macros.pt | 5 +++++ locales/de/LC_MESSAGES/loops.mo | Bin 27548 -> 27585 bytes locales/de/LC_MESSAGES/loops.po | 3 +++ 4 files changed, 13 insertions(+) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index ec580e6..03adf71 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -23,6 +23,7 @@ surveys and self-assessments. import csv from cStringIO import StringIO +import math from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from zope.i18n import translate @@ -269,8 +270,12 @@ class SurveyView(InstitutionMixin, ConceptView): average = float(sum(values)) / len(values) if question.revertAnswerOptions: average = question.answerRange - average - 1 + devs = [(average - v) for v in values] + stddev = math.sqrt(sum(d * d for d in devs) / len(values)) average = average * 100 / (question.answerRange - 1) + stddev = stddev * 100 / (question.answerRange - 1) result['average'] = int(round(average)) + result['stddev'] = int(round(stddev)) texts = [r.texts.get(question) for r in self.teamData] result['texts'] = '
'.join([unicode(t) for t in texts if t]) return result diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 1ae3702..3565e33 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -178,10 +178,12 @@     + Average + + diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 1e9869ae698151828c41f823f3f42902345f7a44..755a2ce9605ddbf6a4f8d32145db66e9d1bd9197 100644 GIT binary patch delta 9749 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 rqO1&d!)B Date: Thu, 23 Apr 2015 14:03:12 +0200 Subject: [PATCH 115/269] preferences questionnaire basically working --- knowledge/survey/browser.py | 42 +++++++++++++++++++- knowledge/survey/view_macros.pt | 69 ++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 03adf71..c109845 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -84,6 +84,10 @@ class SurveyView(InstitutionMixin, ConceptView): def report(self): return self.request.form.get('report') + @Lazy + def questionnaireType(self): + return self.adapted.questionnaireType + def teamReports(self): if self.adapted.teamBasedEvaluation: if checkPermission('loops.ViewRestricted', self.context): @@ -99,11 +103,24 @@ class SurveyView(InstitutionMixin, ConceptView): @Lazy def groups(self): result = [] + if self.questionnaireType == 'pref_selection': + groups = [g.questions for g in self.adapted.questionGroups] + questions = [] + for idxg, g in enumerate(groups): + qus = [] + for idxq, qu in enumerate(g): + questions.append((idxg + 3 * idxq, qu)) + questions.sort() + questions = [item[1] for item in questions] + size = len(questions) + for idx in range(0, size, 3): + result.append(dict(title=u'Question', infoText=None, + questions=questions[idx:idx+3])) + return [g for g in result if len(g['questions']) == 3] if self.adapted.noGrouping: questions = list(self.adapted.questions) questions.sort(key=lambda x: x.title) size = len(questions) - #nb, rem = divmod(size, self.batchSize) bs = self.batchSize for idx in range(0, size, bs): result.append(dict(title=u'Question', infoText=None, @@ -207,6 +224,8 @@ class SurveyView(InstitutionMixin, ConceptView): if self.adapted.teamBasedEvaluation and self.institution: respManager.institutionId = self.getUidForObject( baseObject(self.institution)) + if self.adapted.questionnaireType == 'pref_selection': + return self.prefsResults(respManager, form, action) data = {} response = Response(self.adapted, None) for key, value in form.items(): @@ -280,6 +299,22 @@ class SurveyView(InstitutionMixin, ConceptView): result['texts'] = '
'.join([unicode(t) for t in texts if t]) return result + def prefsResults(self, respManager, form, action): + result = [] + data = {} + for key, value in form.items(): + if key.startswith('group_') and value: + data[value] = 1 + respManager.save(data) + if action == 'save': + self.message = u'Your data have been saved.' + return [] + self.data = data + #self.errors = self.check(response) + if self.errors: + return [] + return result + def check(self, response): errors = [] values = response.values @@ -352,6 +387,11 @@ class SurveyView(InstitutionMixin, ConceptView): if self.data: return self.data.get(question.uid) + def getPrefsValue(self, question): + self.loadData() + if self.data: + return self.data.get(question.uid) + def getCssClass(self, question): cls = '' if self.errors and self.data.get(question.uid) is None: diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 3565e33..64fde8c 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -4,6 +4,9 @@
-
+ +
+ + + + @@ -91,7 +99,64 @@ i18n:attributes="value" onclick="setRadioButtons('none'); return false" /> -
+ + + + +

Questionnaire

+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + +
  
+ +
+ +
+
+
+ + +
+ + + +
From 86421f8cc4c197eaf64bc9c47086470543bc3d46 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 23 Apr 2015 14:24:39 +0200 Subject: [PATCH 116/269] add evaluation for preferences questionnaire --- knowledge/survey/browser.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index c109845..f1bf303 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -109,9 +109,9 @@ class SurveyView(InstitutionMixin, ConceptView): for idxg, g in enumerate(groups): qus = [] for idxq, qu in enumerate(g): - questions.append((idxg + 3 * idxq, qu)) + questions.append((idxg + 3 * idxq, idxg, qu)) questions.sort() - questions = [item[1] for item in questions] + questions = [item[2] for item in questions] size = len(questions) for idx in range(0, size, 3): result.append(dict(title=u'Question', infoText=None, @@ -313,6 +313,14 @@ class SurveyView(InstitutionMixin, ConceptView): #self.errors = self.check(response) if self.errors: return [] + for group in self.adapted.questionGroups: + score = 0 + for qu in group.questions: + value = data.get(qu.uid) or 0 + if qu.revertAnswerOptions: + value = -value + score += value + result.append(dict(category=group.title, score=score)) return result def check(self, response): From a02ccde5ff62041c512b3188111248f5a146c7d0 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 23 Apr 2015 19:13:28 +0200 Subject: [PATCH 117/269] remove group headers for preferences questionnaire --- knowledge/survey/view_macros.pt | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 64fde8c..c5483e0 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -122,20 +122,7 @@ tal:condition="personId" tal:attributes="value personId" /> - -   -   - - - -
- -
- - - +    From 9c71572be8858a9693daf50b2d2a7258990e724b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 24 Apr 2015 10:08:25 +0200 Subject: [PATCH 118/269] handle strange case when user account references a person that has been deleted --- organize/interfaces.py | 15 +++++++++------ organize/party.py | 10 ++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/organize/interfaces.py b/organize/interfaces.py index e550049..b39e841 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -67,10 +67,13 @@ class UserId(schema.TextLine): mapping={'userId': userId})) person = getPersonForUser(context, principal=principal) if person is not None and person != context: - raiseValidationError( - _(u'There is alread a person ($person) assigned to user $userId.', - mapping=dict(person=zapi.getName(person), - userId=userId))) + name = zapi.getName(person) + if name: + raiseValidationError( + _(u'There is already a person ($person) ' + u'assigned to user $userId.', + mapping=dict(person=name, + userId=userId))) class LoginName(schema.TextLine): @@ -82,7 +85,7 @@ class LoginName(schema.TextLine): super(LoginName, self)._validate(userId) if userId in getPrincipalFolder(self.context): raiseValidationError( - _(u'There is alread a user with ID $userId.', + _(u'There is already a user with ID $userId.', mapping=dict(userId=userId))) diff --git a/organize/party.py b/organize/party.py index c83ac40..7cfd52c 100644 --- a/organize/party.py +++ b/organize/party.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -93,9 +93,11 @@ class Person(AdapterBase, BasePerson): return person = getPersonForUser(self.context, principal=principal) if person is not None and person != self.context: - raise ValueError( - 'There is alread a person (%s) assigned to user %s.' - % (getName(person), userId)) + name = getName(person) + if name: + raise ValueError( + 'There is already a person (%s) assigned to user %s.' + % (getName(person), userId)) pa = annotations(principal) loopsId = util.getUidForObject(self.context.getLoopsRoot()) ann = pa.get(ANNOTATION_KEY) From c970507c85657ebb83b8a3f27d69b16f10ddec31 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 26 Apr 2015 13:16:33 +0200 Subject: [PATCH 119/269] provide selectable standard texts for work items via data table 'organize.work.texts' --- browser/common.py | 6 ++++++ organize/work/browser.py | 18 ++++++++++++++++++ organize/work/work_macros.pt | 16 +++++++++++++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/browser/common.py b/browser/common.py index d26966f..05e24c6 100644 --- a/browser/common.py +++ b/browser/common.py @@ -989,6 +989,12 @@ class BaseView(GenericView, I18NView, SortableMixin): jsCall = 'dojo.require("dojox.image.Lightbox");' self.controller.macros.register('js-execute', jsCall, jsCall=jsCall) + def registerDojoComboBox(self): + self.registerDojo() + jsCall = ('dojo.require("dijit.form.ComboBox");') + self.controller.macros.register('js-execute', + 'dojo.require.ComboBox', jsCall=jsCall) + def registerDojoFormAll(self): self.registerDojo() self.registerDojoEditor() diff --git a/organize/work/browser.py b/organize/work/browser.py index 9bc303d..f6be38f 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -380,6 +380,10 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): def checkPermissions(self): return canAccessObject(self.task or self.target) + def setupView(self): + self.setupController() + self.registerDojoComboBox() + @Lazy def macro(self): return self.template.macros['create_workitem'] @@ -404,6 +408,20 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): track.workItemType = types[0].name return track + @Lazy + def titleSelection(self): + result = [] + dt = adapted(self.conceptManager.get('organize.work.texts')) + if dt is None or not dt.data: + return result + names = ([getName(self.target)] + + [getName(p.object) + for p in self.target.getAllParents(ignoreTypes=True)]) + for name, text in dt.data.values(): + if not name or name in names: + result.append(text) + return result + @Lazy def title(self): return self.track.title or u'' diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 7071d7c..959a814 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -70,7 +70,8 @@
+ workItemType view/workItemType; + dummy view/setupView"> @@ -94,8 +95,17 @@ tal:attributes="value python:workItemTypes[0].name" /> -
- + +
From aef3d2e139f8ccbca2f6e7b43053c17f283dc86f Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 26 Apr 2015 13:23:15 +0200 Subject: [PATCH 120/269] change translation for 'activity' --- locales/de/LC_MESSAGES/loops.mo | Bin 27585 -> 27587 bytes locales/de/LC_MESSAGES/loops.po | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 755a2ce9605ddbf6a4f8d32145db66e9d1bd9197..ee176b88a93f1af1295009315f5521f8b94721dc 100644 GIT binary patch delta 3672 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 Oa8X%B-GlMx`~DA4>bpn) diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index dd49d48..af03efd 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.1\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2015-04-21 12:00 CET\n" +"PO-Revision-Date: 2015-04-26 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -1107,7 +1107,7 @@ msgid "Priority" msgstr "Priorität" msgid "Activity" -msgstr "Tätigkeit" +msgstr "Leistungsart" msgid "Action" msgstr "Aktion" From 070cb50a54805cb7c3d5aefc9bbd28e0b668e480 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 26 Apr 2015 15:44:32 +0200 Subject: [PATCH 121/269] show combo box only for new work items not when editing existing ones --- organize/work/browser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/organize/work/browser.py b/organize/work/browser.py index f6be38f..4e0f67d 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -411,6 +411,8 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): @Lazy def titleSelection(self): result = [] + if self.title: + return result dt = adapted(self.conceptManager.get('organize.work.texts')) if dt is None or not dt.data: return result From 0555329e86d755365e9f9a789ee173bcc3b9f05d Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 27 Apr 2015 10:26:04 +0200 Subject: [PATCH 122/269] provide slot on download page for customization --- browser/resource_macros.pt | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/resource_macros.pt b/browser/resource_macros.pt index 8a70e4f..a273dd7 100644 --- a/browser/resource_macros.pt +++ b/browser/resource_macros.pt @@ -96,6 +96,7 @@
+
From 77bf66996a05ce86daca38f954c5f0c891e71fd3 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Wed, 6 May 2015 10:29:25 +0200 Subject: [PATCH 123/269] additional check for restricted editing permission --- browser/common.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/browser/common.py b/browser/common.py index 05e24c6..8ce5796 100644 --- a/browser/common.py +++ b/browser/common.py @@ -75,6 +75,7 @@ from loops.organize.util import getRolesForPrincipal from loops.resource import Resource from loops.security.common import checkPermission from loops.security.common import canAccessObject, canListObject, canWriteObject +from loops.security.common import canEditRestricted from loops.type import ITypeConcept, LoopsTypeInfo from loops import util from loops.util import _, saveRequest @@ -867,6 +868,10 @@ class BaseView(GenericView, I18NView, SortableMixin): def canAccessRestricted(self): return checkPermission('loops.ViewRestricted', self.context) + @Lazy + def canEditRestricted(self): + return canEditRestricted(self.context) + def openEditWindow(self, viewName='edit.html'): if self.editable: if checkPermission('loops.ManageSite', self.context): From b34b5705e022d71da3039e1714ea5a4c8e9ec94f Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 14 May 2015 14:57:45 +0200 Subject: [PATCH 124/269] correctly position div element for headline and body of concept subpage --- browser/node_macros.pt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/browser/node_macros.pt b/browser/node_macros.pt index 41d8a10..8965958 100644 --- a/browser/node_macros.pt +++ b/browser/node_macros.pt @@ -30,7 +30,7 @@ item nocall:target" tal:attributes="class string:content-$level; id id; - ondblclick python: target.openEditWindow('configure.html')"> + ondblclick python:target.openEditWindow('configure.html')"> The body @@ -41,17 +41,22 @@ - +
+ ondblclick python:item.openEditWindow('configure.html')"> Node Body
- -
- +
+
+
+
+
From 4c13744a0cd28e380c6d3add7e587734b125884b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 18 May 2015 10:54:58 +0200 Subject: [PATCH 125/269] set date/time fields correctly when selecting 'start' action --- browser/loops.js | 9 +++++++++ organize/work/browser.py | 10 ++++++++++ organize/work/work_macros.pt | 14 +++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/browser/loops.js b/browser/loops.js index bb24e2c..0f200a4 100644 --- a/browser/loops.js +++ b/browser/loops.js @@ -47,6 +47,15 @@ function showIfIn(node, conditions) { }) } +function setIfIn(node, conditions) { + dojo.forEach(conditions, function(cond) { + if (node.value == cond[0]) { + target = dojo.byId(cond[1]); + target.value = cond[2]; + } + }) +} + function destroyWidgets(node) { dojo.forEach(dojo.query('[widgetId]', node), function(n) { w = dijit.byNode(n); diff --git a/organize/work/browser.py b/organize/work/browser.py index 4e0f67d..8eef9a8 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -476,6 +476,16 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): return getTimeStamp() return None + @Lazy + def defaultDate(self): + return format.formatDate(date.today(), 'date', 'medium', + self.languageInfo.language) + #return time.strftime('%Y-%m-%d', time.localtime(getTimeStamp())) + + @Lazy + def defaultTime(self): + return time.strftime('%H:%M', time.localtime(getTimeStamp())) + @Lazy def date(self): ts = self.track.start or self.defaultTimeStamp diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 959a814..41ca9c2 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -120,12 +120,21 @@ + +
From 4833ebd54baf830840b3e0faa802c8610be30641 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 21 May 2015 08:17:02 +0200 Subject: [PATCH 126/269] fix setting of date and time fields on 'start' action --- browser/loops.js | 4 ++-- organize/work/browser.py | 8 +------- organize/work/work_macros.pt | 20 +++++++++----------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/browser/loops.js b/browser/loops.js index 0f200a4..483f352 100644 --- a/browser/loops.js +++ b/browser/loops.js @@ -50,8 +50,8 @@ function showIfIn(node, conditions) { function setIfIn(node, conditions) { dojo.forEach(conditions, function(cond) { if (node.value == cond[0]) { - target = dojo.byId(cond[1]); - target.value = cond[2]; + target = dijit.byId(cond[1]); + target.setValue(cond[2]); } }) } diff --git a/organize/work/browser.py b/organize/work/browser.py index 8eef9a8..ef5686d 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -478,13 +478,7 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): @Lazy def defaultDate(self): - return format.formatDate(date.today(), 'date', 'medium', - self.languageInfo.language) - #return time.strftime('%Y-%m-%d', time.localtime(getTimeStamp())) - - @Lazy - def defaultTime(self): - return time.strftime('%H:%M', time.localtime(getTimeStamp())) + return time.strftime('%Y-%m-%dT%H:%M', time.localtime(getTimeStamp())) @Lazy def date(self): diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 41ca9c2..03a1507 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -122,10 +122,12 @@ onChange="showIfIn(this, [['move', 'target_task'], ['delegate', 'target_party']]); setIfIn(this, [['start', 'start_date', - this.form.default_date.value], + this.form.default_date.value], ['start', 'start_time', - this.form.default_time.value], - ['start', 'end_time', '']])"> + this.form.default_date.value], + ['start', 'end_time', null], + ['start', 'duration', ''], + ['start', 'effort', '']])">
@@ -250,11 +248,11 @@
- / -
From a97ea1b5d102267da2029960be60d141654fb060 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 28 May 2015 15:01:27 +0200 Subject: [PATCH 127/269] allow fine-tuning of answer option header cells via colspan and CSS class --- knowledge/survey/interfaces.py | 4 +++- knowledge/survey/view_macros.pt | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py index 49ab653..500c60f 100644 --- a/knowledge/survey/interfaces.py +++ b/knowledge/survey/interfaces.py @@ -67,7 +67,9 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire): answerOptions.column_types = [ schema.Text(__name__='value', title=u'Value',), schema.Text(__name__='label', title=u'Label'), - schema.Text(__name__='description', title=u'Description'),] + schema.Text(__name__='description', title=u'Description'), + schema.Text(__name__='colspan', title=u'ColSpan'), + schema.Text(__name__='cssclass', title=u'CSS Class'),] noGrouping = schema.Bool( title=_(u'No Grouping of Questions'), diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index c5483e0..3a0ac0c 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -76,11 +76,13 @@
- From 2e76910134c60d876631966946e43459fe47f52b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 1 Jun 2015 08:39:46 +0200 Subject: [PATCH 128/269] make sure management views of records/tracks can only accessed by managers --- organize/tracking/configure.zcml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/organize/tracking/configure.zcml b/organize/tracking/configure.zcml index 016ddf5..b1b0867 100644 --- a/organize/tracking/configure.zcml +++ b/organize/tracking/configure.zcml @@ -61,7 +61,7 @@ for="cybertools.tracking.interfaces.ITrackingStorage" name="index.html" class="cybertools.tracking.browser.TrackingStorageView" - permission="zope.View" /> + permission="loops.ManageSite" /> + permission="loops.ManageSite" /> + permission="loops.ManageSite" /> + permission="loops.ManageSite" /> Date: Fri, 5 Jun 2015 13:54:35 +0200 Subject: [PATCH 129/269] directly reference questionnaire as question may be assigned to more than one questionnaire --- knowledge/survey/browser.py | 9 +++++---- knowledge/survey/view_macros.pt | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index f1bf303..48a341f 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -278,21 +278,22 @@ class SurveyView(InstitutionMixin, ConceptView): result.append(item) return result - def getTeamResultsForQuestion(self, question): + def getTeamResultsForQuestion(self, question, questionnaire): result = dict(average=0.0, stddev=0.0) if self.teamData is None: respManager = Responses(self.context) self.teamData = self.getTeamData(respManager) + answerRange = question.answerRange or questionnaire.defaultAnswerRange values = [r.values.get(question) for r in self.teamData] values = [v for v in values if v is not None] if values: average = float(sum(values)) / len(values) if question.revertAnswerOptions: - average = question.answerRange - average - 1 + average = answerRange - average - 1 devs = [(average - v) for v in values] stddev = math.sqrt(sum(d * d for d in devs) / len(values)) - average = average * 100 / (question.answerRange - 1) - stddev = stddev * 100 / (question.answerRange - 1) + average = average * 100 / (answerRange - 1) + stddev = stddev * 100 / (answerRange - 1) result['average'] = int(round(average)) result['stddev'] = int(round(stddev)) texts = [r.texts.get(question) for r in self.teamData] diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index 3a0ac0c..da349cb 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -243,7 +243,8 @@ + data python: + item.getTeamResultsForQuestion(question, item.adapted)">
Date: Sat, 6 Jun 2015 09:53:55 +0200 Subject: [PATCH 130/269] confirm resetting all response data entered --- knowledge/survey/view_macros.pt | 8 ++++---- locales/de/LC_MESSAGES/loops.mo | Bin 27587 -> 27817 bytes locales/de/LC_MESSAGES/loops.po | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt index da349cb..6a0b7ac 100644 --- a/knowledge/survey/view_macros.pt +++ b/knowledge/survey/view_macros.pt @@ -98,8 +98,8 @@ + i18n:attributes="value; onclick" + onclick="if (confirm('Do you really want to reset all response data?')) setRadioButtons('none'); return false" /> @@ -143,8 +143,8 @@ + i18n:attributes="value; onclick" + onclick="if (confirm('Do you really want to reset all response data?')) setRadioButtons('none'); return false" /> diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index ee176b88a93f1af1295009315f5521f8b94721dc..48c8944d51b815d1407a3844c69ccc2bc42150dd 100644 GIT binary patch delta 8127 zcma*rd0drM9>?+L0s>bNSyUuhE`b6fu857{61Zg060>nBomA4XMs3ryLFe=3IlW%<=kR)6-{+j?EWh(R=ebb(|KVDD z#^pU9=C{b==cvoFqOd5~vhsW^YnLkg^l8cm`(g^NKz}@LJcR+&zrsMgjK279tb;#b zU984ftdnS2A((=0%ko+o6oP2zj;w9dyty&kH61Jv_qQ*Vk|rv-+wzLjY@JcVxR&!AG2kIKw6N6q&$YT;VVEGq&d(W{QFDQJgTsE+;dLCnQSoQ?JIUDUvBsELoGCcKO~ zieFLl)#IXQo;Xy!4K~8A7>2`8M>al%{OinT(4d{pLj|$~HSs!p0QaC4JcA181}YGD zbEiEH)xSL|(8o~yMqnIHH|4^%dyGHK@y5Zk}&7?m=z%5Z1vjHGu|PG!MQLI99 z#+df@s6e`)GB+4CZmg-7pf={kV6;&iT7f}o|B!;tYAR(qpKd zoj@&o4s}!)P$~Wv)9|ioZ`s=EpNmf_AXXxC*u4M&lOL!rM`S z?ltwJr~tk|jX#IF3ztwktwsgv+r|kn07IxZM)hls-dqaZDTLyB)EVwZWuOwZvy0~W zEmSH4+d4anMV)Ob>UGS(aD39V=bQE^sLS>mDicdlmv~)U@~<82r$IY7irQf%)}$D9 zRu@nMzC&fgk37p@RHhoCCXP1s6yy}FR@fVJFbp?i6WoVd?`j(PS7bNMgKAWY;@deL z(u^6XeqB+i?TZR@0BWHTsEsVZ23Ur=%$rcZNOxc=eu+uwm(KS;rg|wPQW%N}I13x& zTGRpuP)Bjx)X$?Lzhc^NpaQyYp4V#cWFi>#_C%R_d#q2r8}`7#sLXj+P|z8!K`poq z!|`KON>5?~{Kh=Ljmm`ekaOmBQRAARj--XDKZM%YBc@(}T5tkt{1l|W*P2B^JDiW& z$#PUG-$R}KdQ^bhP5mG$LzSq7ucP|kM)kXg{2sFE{=u?5_z>#s2cd3#5h~L!V}#!S z^%OMGe$?eShKlq@R6w^;fmEY*=A#U^2Et1vDJ>R*gpWn~MBlWz9ex z;Y{?``#+b0QvNz>;rCDru0;*piduLl>QWsr_0LWHEb6GLkZWN5WNh}ZQ_n#KQicj( z9qKY}MEx~oouk03ZTV(c)+kKJKDY!M<9DdP2Ld`enQDd#q%A7IPN>V(74_N#$N5e-U787c$spf24i({ZQqFslD4RKL@xabII2tU}$Td#Kkk zxD(mM&ZvGzP=TI61@dht@~^Z1fd)-@7q!5BRLXri^U7cl@@;DML+yALYTPTRaW*R8 zGSns9gj!&?X+MnWe;##ISMUM+#Y>?%g^(`JpT`|hmuDDiK(28ds(mu*Hb0L__3Nme zEkyNSit4utwSf(&z_wvSJcbJF5~{!VItA_MHfmrsl0D1s5$AQuL=7B@+Sx?Zf-j&Z zcm;L2Y}A6QOno!zt=Nk?l1fv*h&rmDoOZ7jl@^-iKiW^AQuQV3)?P*3fm_B} z-JE)TV?3&V8&u$3QSW^pR0eWUfjx@~Yf~QUUaMVIYs2xs49pQY`H{1q{z_aL!KVr=WQ2lH5B>yTj?&(b28g=P9pd#;$ zy8Ss=vw&%zk2-=Cr~o#gcK8vh{{hq`{R#u|dsP2lO#MD8ut4vl&I6CJ8S0GFP!o5< z;n)|InR3)o+%o$0axP^Ux@nI#^+XJy-Ujv7bU@9QjS6T8Y94Q{=`g``n2k!cZCrwS z8{S1txCb@xAlAk&P#L>`O8t+horOH+e8VN7>RG6MLr{05*wJhKiGqiQH&CgoKn*yG zdhITn_Wz)E68^aJJPBi|cR@`!0=2*d)VP;W{ogk29~uv#HgFbe-v6rX(hmU?Hmi9MlF^VzS=WGT5E>1$-?iW#KJKOj=>MkwDAY6x~S&380EP#Mfc4^Btz^exlA4RtifP)B^hwBPE( z`>!(!>g$|U9O~@SQ7P+V>f=xW%|-?A4r+mqFceQ>Fjk@F``PI3=k$+31(=S?@Z%VQ zBl?klH9Sj$&i*A-gln+owxcFKi29;BWjt%VfGugijA2;6zY{WDH-JqznlABdVa z7nQjpFNJUlQ&2nmGwO_2qTc7V=z|rg3AbTA+>IUa@2CmuJn5W$I4a;MV+uYOuB5FSGs}!^|+gOH*bQ$VWtwja05jAkT@e@=4M^FI=JmvJSi`qyiDkI6LqsqX^ zH~_VQYm|S%TX6dhCJaI27-o z?$Xl(os>^S9no^s23BJVZpDUr|G%N2)c%SJz%|IZ9Kps&)bj*<0#i{FK8H%}EL4DR zqjtI()36eoW58hNzk<^+iTVqeitEuEPvHy&oq07jME4LU01ql8SA?hmn6Z^rS%(Wuwk$2rA`cQISqW z4Va7Sw-6QZJD7nLs7&6(NUZ;ilYti4ntFHCotTQ+&=PEe?|UgI;&Z4#E}0J3O#N5m zeWO45Y(&4h7==wy0cD{A%0?Z{NYwX6zNr_Y)_E57woNm2?>tjjgxc{kRO(ivI(~q< zET5t#_z|_hU2Komh?@TuU_FGoY|o+sS&j;z9CaB##2EY+cEG?Kva0t#i$Xsdwqr8Z z9_hR`X{eMBMg^9KTDTaM(o(E>olN^yRR5i**YqGNqo+~*E}#!yN6q&G`s)3^OQA6h z_fR{2V3c!NTA(t~2{oXnY43~C)N@b^dGS$PfPcets7$TNb$%CYMxFVmsQD^U&%Z(+ z*0(NG&;nO58*gGJcFA*g{32=xbI}jWP&-|QO7;7ww`C{lZX8C<_YG>?HB{>FU^+G$ z?Q9?$z3MoQf+n17oQbulzlzGp8>maR+PDS%sqaDU=zwYe4E5fhL%nW4p?2;&#t9@8 z6{rW*o;rs7Yo`y>p!a(Q_QFM`!#^>A`gK%*x6lV&`OdYY)k{s1-K$Ed&# zp%y-k8efIl`At-&?s+LF#SO{sNRZ8&PeOwP-i_5HGUG- z!Iv-y7hr8%jxIdLXO(iZ+1?S=K5_$X%lMq7`S+hN>gBdOI@YzwPL7Uu?YDbJM?`)= z>($@87@wqVx&3@}WA|1{Z_~Qnem6SNy^)%J_Sz?+JG(x#Lt{!@o9vk}EnVB}@|f6K zpQ^Wgiqd)0&U5j)Nk5q6j!Ynba7J}W7&aM`^) z4>s6CQ9nB!YW{uKp6PkG!9kuZ=95g@JRFCAu}^p!hwe0E^sS(u_w8y=L}&%A%Ge=3 zoB6D*w4rJckf{M ze1?5upNUU&ucmgK+FIM4Q0V%|o|KT%Z69sA%<+6yQU3j>6XgL^4w<${EaS6~wx)Oh z-!jkE*;NT|yWX^4YLepGT~XfTbkxYvo}^JF#iI)*j7v)FTH={jGRZR`FQ=$znrBK* zajB=YgzCh+QV&fkjV~#lnCHpODb4Aen4Ik4Nxz)jf|AThrKLPcN-QoZ&Pz;wn69Oh zCKP){=M+t>cscW1AK&!URuv`PhQ@pUcfkQAMMZhVp8f@So+$+r3X2Lx0^f2U8Huz%O6LROrfSNwl3o@W&l=MJi})|@ka&huAQx0bKIs{0_@qe)nHfa?_=~0x73Mtf}p$oEh)H~FNp%aZmCmJ8xr(*-^zru#N7+qi! zGGVkkJTJp4)Q^YyM_8HqB{a~h<@IC~Rcaj%tbz4-&=Bp=79G$DYhzcef_LC>9ESaH z16IZJ!Edn=_3LQ=N^RojHPL!Qbe$Z`VEw3L=+GC_so#mFXe64MDZxk3h2~&gT!03+ zCOlt{>C|_i&-bF4I*QJB7G3xfW@36<@^8m{3huBI+Oan_!x5N;KgasG2pzZqop>KQ z;a||BIET)69i68x7sh%sY=p&_frHQ^yQdxJ@0m}d!JR&e1~MO=cm+1bt>}Vpq5*w` z2682|*DZ|uw?YHG740_!^KoiuUx3c@9A@M0Lh^4!@6q6b=db{;B6CN1?c*H`M9*$4 zdIXcu1*V7klUR}ZVob+n=;eJbJYOH&if(u}R>wDz6ddqwc<>>5reC5HevK}88EazH zAx>p2w7nG?$c<>`?m)+l3iWa5#-?K}oP!?8VyuD5brd|S?dSvt(1qT`TKF-Vp$lm0 zD%=oH*a#h0h)&oYP5oeWLleUDY3Mvjbl#^z`zj=WWV9tbcp0nF;c%!QL(l#+*2b%7 zW@_9RPgD<0aXxx9Ezpd#$2!;r9oHYdoOhwGt`0trF1!&9 zbbF}3j0W&JI{pZH7mlGjJ&y+Z4I1EOtb^4%#{HUNasmxqDAdK3=oxN7GjISM_-=Uq z8Jf!P&>hw86rXK2`Z^Y({riOWk)eGumR~k>+yeCbU_~eL?+!|7a0f4=J3N5pDMrug z9dy74XeKTOuc4W$O5UBgCR%TToI;d`gK#Kj;96{fJJ9t`6qA1=`#3x}kEWL&LPOAv%*F;d553H5&>zxG*dE`&)_4(lp`z?$7v5nC1F<>I#3r~L zU0^4A6#GK`C>r_k(Ebq`(1q~)5}Ju?=-X4{rnue;>r?NH{qYVoQ^~~?d_R|=3vR%A zxDB1?HEe)?4$nVFGw~11#P36UgPY@{$wB97jc%+c)bBSG&hfd(`feXD+q&T}8~536VzdW8Ri zm3{wbQ849?qYE!V7hH}GT#qhXf-bx>)L#wtx6q?{4>|VeOtA5-aeXKn$UHQF73gJL zjY-ew2nAm0=o=i5ExX14=9`aAsDFU|d*Cvfsm9&oKnlC&p<_vK58D4A+V3zr?rm&@@1b|;EA+LzhEL)R zJ>q^Zp@Ej6fxO>?{JW#mG&tcG=mHnelz)wV@hbA|8TGm?-tkOy+%M2^bI^e2p_gzC zy1wh`T~~C%!QuHhbfFno zeiY~q=b__L=sc^?K)0d&%gXCXe%&Z!({L7>Vujvugt=%2Zbl~@hGuF^a6G!e6g1!& zXl7=ify_p4`LEG&DNMuVp?x)0Wc}zx3J%;B+>O5f`$PQ*8u2kS#b-nNSHW+?^J{28 zwfn>YHA2rm7oEQ|`n)?DP(MuiU|{Gl0xMDGo#1q6{{mg;GP=XG z{_zoJq2Fvbp+8O&F%2KZ@(rN3o z+=K?&8|^;?y_}P=D*hbp|9Gg+M*~ZR_L5{M>_pFaKRWTBa2$S!W~SSK_$X!vpGGg` zYD~x9hWZYyLj4u=Z8?a}cM_}OXXue$2+xz>hYlHc#Hr2+wm{#8LUh6b=)j>^4acDw zn~J_YkD@zUiGH({hWb&o-)HC@xe~1Y!}6c!WRy+8)b&6o_!0WrJs8^m9o@;A@Vpdr zsUJZn{1RQ@dvsi#fpPz~X!~u!Vdw@XqM3de8~FP_mqI=bE6~XIVkiw`Q>qie!@Y0o_XS5%y;bC;A z$I$on8?201uqH->ffOOWekl2Xose0T= zvltEJW^`b$;1AINMxX&NM*BaDrhFA<;P26+I)snlC+G(5{!x4+Q?MTEM@b64UMcjf zx1$rig>~^0bY~aQOO*EG_<3XWc}Mhh>w_+Q4|+)-Ml&!Q&EPsTV? zYjOm=d?(NeE};>p-4!oX54}7s(4BX|{@4vi<6QJEokmlhHZoqY4Z49MY=^gFLwo?M z`u@+MU;qoy%dtGT&JNVK;hne#-9eSRkZ4jz1jg$3~NXCpt-kXY?7G@=NFh*U$m=$He_|(SX}wH|&9?@|T!}YcUgF!Xo@L zdMDDx#v5vZ9jSLh1D=$mU}V!mho6P|oZ$T6B5XvzXR#@6M*}~K26PfVn)B%Q#y6q< zExOQE^wwA8T&y<=Ci5t`^3y+{Xt~D{5 zYc$oJ(6^-@dN+on0Y8B5{AXzD=VBLp0o}k!wBL7F{`;Sp6i-|OEAb!`%}6$S$%=yA zu`=}m=#GYj_EG4qpM+-cF?8oop@FPI11$;dd(e%(F^T;9ey307|43mTI`DopRkP3? z&qhrtKit+r29C3-}7lSRK>YyKnt-l zu0scwU{%}|>if~1y@gKrS2Up0=uW>z$6ZG^RBcL}p+;zCZw&THQb?y^Bzi0FMUP-o z@BuWXGthZJiCn*^D8FZnG!HQGk z$m?S|?ak0LE<_jZ6WWKM-+ZG&eI~llW9ZI*9b6KgKaVcF83`~M?WEwshp-5bV|`4& zKTdHDdZz8s2}h#~OhwOl20H#}td483{IX#+>U%JOf8?{C&sHjK$iHz>>iwo|vr1@M z#ph()TK?Y*>c2~6W#=ZIOWl%PkT{SUlbxBhjn;Gjub0IUw5?7p%x;qYdrE6**_GO! z-70-EHUC~sozA{3QJTunnV#5^T9k7`VrQx>C%4jTJYSRgl+p{7UQeaxwn{8d73Dsi zcr#U&+b*#(bw0Ou;&7@cFEis1JvK7eB|guW&zl;R*Q~)S<;5uagX@I9|4c2)yS2eV zo;=H^4Xq3DL0pc0wFrLU)Ul-h4o zS`pz=yj|Zi+Pv5WeAZF^zrP-o@1pWXXv@N7e3sDG5)a~v@N84+QuDQm-=vnd iXqVVqTGrx3)6#3b&QwS%Zr`!=Y~RrZrQ4@`SK*(Q3#cFf diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index af03efd..bfa8d88 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.1\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2015-04-26 12:00 CET\n" +"PO-Revision-Date: 2015-06-06 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -348,6 +348,9 @@ msgstr "Abweichung" msgid "Team Size" msgstr "Anzahl der vom Team ausgefüllten Fragebögen" +msgid "if (confirm('Do you really want to reset all response data?')) setRadioButtons('none'); return false" +msgstr "if (confirm('Wollen Sie wirklich alle eingegebenen Daten zurücksetzen?')) setRadioButtons('none'); return false" + # compentence and qualification management msgid "Validity Period (Months)" From 5bf2906c514902bad7f03a883c58ccdcae9d770d Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 12 Jun 2015 07:20:01 +0200 Subject: [PATCH 131/269] avoid error when no report is found --- expert/browser/report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/expert/browser/report.py b/expert/browser/report.py index a24d1bd..940a89b 100755 --- a/expert/browser/report.py +++ b/expert/browser/report.py @@ -184,7 +184,8 @@ class ResultsConceptView(ConceptView): if not reports: type = self.context.conceptType reports = type.getParents([self.hasReportPredicate]) - return adapted(reports[0]) + if reports: + return adapted(reports[0]) @Lazy def reportInstance(self): From 70e53daadf088c04fc43677574f263353719263b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 15 Jun 2015 07:33:27 +0200 Subject: [PATCH 132/269] send email form: only accessible for logged-in users --- organize/browser/party.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/organize/browser/party.py b/organize/browser/party.py index 6256d2a..3f9a78b 100644 --- a/organize/browser/party.py +++ b/organize/browser/party.py @@ -133,6 +133,10 @@ class SendEmailForm(NodeView): __call__ = innerHtml + def checkPermissions(self): + return (not self.isAnonymous and + super(SendEmailForm, self).checkPermissions()) + @property def macro(self): return organize_macros.macros['send_email'] @@ -181,6 +185,10 @@ class SendEmailForm(NodeView): class SendEmail(FormController): + def checkPermissions(self): + return (not self.isAnonymous and + super(SendEmail, self).checkPermissions()) + def update(self): form = self.request.form subject = form.get('subject') or u'' From 5fd52269afc0d8952660835979239069c94b1dde Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 15 Jun 2015 12:49:00 +0200 Subject: [PATCH 133/269] no version number when object is not versioned --- organize/tracking/report.pt | 2 +- organize/tracking/report.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/organize/tracking/report.pt b/organize/tracking/report.pt index 41ee48f..a34ed9f 100644 --- a/organize/tracking/report.pt +++ b/organize/tracking/report.pt @@ -79,7 +79,7 @@ - 1.1 diff --git a/organize/tracking/report.py b/organize/tracking/report.py index d1ee35e..a019865 100644 --- a/organize/tracking/report.py +++ b/organize/tracking/report.py @@ -275,7 +275,9 @@ class TrackDetails(BaseView): else: title = view.listingTitle versionable = IVersionable(self.object, None) - version = versionable is not None and versionable.versionId or '' + version = ((versionable is not None and + not (versionable.notVersioned) and + versionable.versionId) or '') return dict(object=obj, title=title, type=self.longTypeTitle, url=url, version=version, canAccess=canAccessObject(obj)) From e56b0c98631fe80e8f5a38578eea58ec1194201e Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 23 Jun 2015 14:52:47 +0200 Subject: [PATCH 134/269] show views for loops container and view manager only for site managers --- browser/configure.zcml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/configure.zcml b/browser/configure.zcml index 30135df..f63ba11 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -123,7 +123,7 @@ @@ -363,7 +363,7 @@ Date: Thu, 25 Jun 2015 16:21:22 +0200 Subject: [PATCH 135/269] url encoding fix --- browser/common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/browser/common.py b/browser/common.py index 8ce5796..e715c53 100644 --- a/browser/common.py +++ b/browser/common.py @@ -997,7 +997,7 @@ class BaseView(GenericView, I18NView, SortableMixin): def registerDojoComboBox(self): self.registerDojo() jsCall = ('dojo.require("dijit.form.ComboBox");') - self.controller.macros.register('js-execute', + self.controller.macros.register('js-execute', 'dojo.require.ComboBox', jsCall=jsCall) def registerDojoFormAll(self): @@ -1043,7 +1043,7 @@ class LoggedIn(object): code = 'error' message = self.messages[code] return self.request.response.redirect(self.nextUrl(message, code)) - + def nextUrl(self, message, code): camefrom = self.request.form.get('camefrom', '').strip('?') url = camefrom or self.request.URL[-1] @@ -1053,6 +1053,7 @@ class LoggedIn(object): params = parse_qsl(qs) params = [(k, v) for k, v in params if k != 'loops.messages.top:record'] params.append(('loops.messages.top:record', message.encode('UTF-8'))) + url = url.encode('utf-8') return '%s?%s' % (url, urlencode(params)) # vocabulary stuff From dbc91c7e6f40646316dfe956253bc0f729f500d6 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 29 Jun 2015 16:17:57 +0200 Subject: [PATCH 136/269] allow easy retrieval of more than one questionnaire per question group --- knowledge/survey/base.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/knowledge/survey/base.py b/knowledge/survey/base.py index cacdcc7..7e2ebd1 100644 --- a/knowledge/survey/base.py +++ b/knowledge/survey/base.py @@ -65,12 +65,18 @@ class QuestionGroup(AdapterBase, QuestionGroup): 'questionnaire', 'questions', 'feedbackItems') _noexportAttributes = _adapterAttributes - @property - def questionnaire(self): + def getQuestionnaires(self): + result = [] for p in self.context.getParents(): ap = adapted(p) if IQuestionnaire.providedBy(ap): - return ap + result.append(ap) + return result + + @property + def questionnaire(self): + for qu in self.getQuestionnaires(): + return qu @property def subobjects(self): From 146b1c78aa4a8bbfae4ab52077a65e446b7790ce Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 16 Jul 2015 15:58:33 +0200 Subject: [PATCH 137/269] allow control of question group selection in subclass; provide principal/person also if request is not given --- knowledge/survey/base.py | 5 ++++- organize/party.py | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/knowledge/survey/base.py b/knowledge/survey/base.py index 7e2ebd1..a9fb732 100644 --- a/knowledge/survey/base.py +++ b/knowledge/survey/base.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -46,6 +46,9 @@ class Questionnaire(AdapterBase, Questionnaire): @property def questionGroups(self): + return self.getQuestionGroups() + + def getQuestionGroups(self): return [adapted(c) for c in self.context.getChildren()] @property diff --git a/organize/party.py b/organize/party.py index 7cfd52c..fbb879c 100644 --- a/organize/party.py +++ b/organize/party.py @@ -24,6 +24,7 @@ from persistent.mapping import PersistentMapping from zope import interface, component from zope.app.principalannotation import annotations from zope.app.security.interfaces import IAuthentication, PrincipalLookupError +from zope.app.security.interfaces import IUnauthenticatedPrincipal from zope.component import adapts from zope.interface import implements from zope.cachedescriptors.property import Lazy @@ -44,6 +45,7 @@ from loops.predicate import RelationAdapter from loops.predicate import PredicateInterfaceSourceList from loops.security.common import assignOwner, removeOwner, allowEditingForOwner from loops.security.common import assignPersonRole, removePersonRole +from loops.security.common import getCurrentPrincipal from loops.security.interfaces import ISecuritySetter from loops.type import TypeInterfaceSourceList from loops import util @@ -59,7 +61,10 @@ def getPersonForUser(context, request=None, principal=None): if context is None: return None if principal is None: - principal = getattr(request, 'principal', None) + if request is not None: + principal = getattr(request, 'principal', None) + else: + principal = getPrincipal(context) if principal is None: return None loops = context.getLoopsRoot() @@ -74,6 +79,15 @@ def getPersonForUser(context, request=None, principal=None): return pa.get(util.getUidForObject(loops)) +def getPrincipal(context): + principal = getCurrentPrincipal() + if principal is not None: + if IUnauthenticatedPrincipal.providedBy(principal): + return None + return principal + return None + + class Person(AdapterBase, BasePerson): """ typeInterface adapter for concepts of type 'person'. """ From 8256a4efea2c03ae4eac573f318d71e6aa2fd92e Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 16 Jul 2015 20:01:34 +0200 Subject: [PATCH 138/269] allow selection of question groups via personId --- knowledge/survey/base.py | 2 +- knowledge/survey/browser.py | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/knowledge/survey/base.py b/knowledge/survey/base.py index a9fb732..467cdff 100644 --- a/knowledge/survey/base.py +++ b/knowledge/survey/base.py @@ -48,7 +48,7 @@ class Questionnaire(AdapterBase, Questionnaire): def questionGroups(self): return self.getQuestionGroups() - def getQuestionGroups(self): + def getQuestionGroups(self, personId=None): return [adapted(c) for c in self.context.getChildren()] @property diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 48a341f..888051e 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -62,9 +62,8 @@ class SurveyView(InstitutionMixin, ConceptView): @Lazy def title(self): title = self.context.title - personId = self.request.form.get('person') - if personId: - person = adapted(getObjectForUid(personId)) + if self.personId: + person = adapted(getObjectForUid(self.personId)) if person is not None: return '%s: %s' % (title, person.title) return title @@ -80,6 +79,10 @@ class SurveyView(InstitutionMixin, ConceptView): return '' return qs + @Lazy + def personId(self): + return self.request.form.get('person') + @Lazy def report(self): return self.request.form.get('report') @@ -104,7 +107,8 @@ class SurveyView(InstitutionMixin, ConceptView): def groups(self): result = [] if self.questionnaireType == 'pref_selection': - groups = [g.questions for g in self.adapted.questionGroups] + groups = [g.questions for g in + self.adapted.getQuestionGroups(self.personId)] questions = [] for idxg, g in enumerate(groups): qus = [] @@ -126,7 +130,7 @@ class SurveyView(InstitutionMixin, ConceptView): result.append(dict(title=u'Question', infoText=None, questions=questions[idx:idx+bs])) else: - for group in self.adapted.questionGroups: + for group in self.adapted.getQuestionGroups(self.personId): result.append(dict(title=group.title, infoText=self.getInfoText(group), questions=group.questions)) @@ -185,7 +189,7 @@ class SurveyView(InstitutionMixin, ConceptView): uid = self.getUidForObject(c) data = respManager.load(uid, instUid) if data: - resp = Response(self.adapted, None) + resp = Response(self.adapted, self.personId) for qu in self.adapted.questions: if qu.questionType in (None, 'value_selection'): if qu.uid in data: @@ -195,7 +199,7 @@ class SurveyView(InstitutionMixin, ConceptView): else: resp.texts[qu] = data.get(qu.uid) or u'' qgAvailable = True - for qg in self.adapted.questionGroups: + for qg in self.adapted.getQuestionGroups(self.personId): if qg.uid in data: resp.values[qg] = data[qg.uid] else: @@ -227,7 +231,7 @@ class SurveyView(InstitutionMixin, ConceptView): if self.adapted.questionnaireType == 'pref_selection': return self.prefsResults(respManager, form, action) data = {} - response = Response(self.adapted, None) + response = Response(self.adapted, self.personId) for key, value in form.items(): if key.startswith('question_'): if value != 'none': @@ -265,7 +269,7 @@ class SurveyView(InstitutionMixin, ConceptView): respManager = Responses(self.context) self.teamData = self.getTeamData(respManager) response = Response(self.adapted, None) - groups = self.adapted.questionGroups + groups = self.adapted.getQuestionGroups(self.pesonId) teamValues = response.getTeamResult(groups, self.teamData) for idx, r in enumerate(teamValues): group = r['group'] @@ -314,7 +318,7 @@ class SurveyView(InstitutionMixin, ConceptView): #self.errors = self.check(response) if self.errors: return [] - for group in self.adapted.questionGroups: + for group in self.adapted.getQuestionGroups(self.personId): score = 0 for qu in group.questions: value = data.get(qu.uid) or 0 @@ -333,7 +337,7 @@ class SurveyView(InstitutionMixin, ConceptView): text='Please answer the obligatory questions.')) break qugroups = {} - for qugroup in self.adapted.questionGroups: + for qugroup in self.adapted.getQuestionGroups(self.personId): qugroups[qugroup] = 0 for qu in values: qugroups[qu.questionGroup] += 1 From d834ec2e16d6d98635065358fb3e9a2e1c67b951 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 20 Jul 2015 08:28:45 +0200 Subject: [PATCH 139/269] allow selection of question groups by person also in ungrouped presentation --- knowledge/survey/base.py | 5 ++++- knowledge/survey/browser.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/knowledge/survey/base.py b/knowledge/survey/base.py index 467cdff..3d0bc28 100644 --- a/knowledge/survey/base.py +++ b/knowledge/survey/base.py @@ -53,7 +53,10 @@ class Questionnaire(AdapterBase, Questionnaire): @property def questions(self): - for qug in self.questionGroups: + return self.getQuestions() + + def getQuestions(self, personId=None): + for qug in self.getQuestionGroups(personId): for qu in qug.questions: #qu.questionnaire = self yield qu diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 888051e..011c6b9 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -122,7 +122,7 @@ class SurveyView(InstitutionMixin, ConceptView): questions=questions[idx:idx+3])) return [g for g in result if len(g['questions']) == 3] if self.adapted.noGrouping: - questions = list(self.adapted.questions) + questions = list(self.adapted.getQuestions(self.personId)) questions.sort(key=lambda x: x.title) size = len(questions) bs = self.batchSize From bbd87c570722362c7544704dabb172250a7404af Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 24 Jul 2015 11:18:00 +0200 Subject: [PATCH 140/269] fix typo --- knowledge/survey/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index 011c6b9..d08cab2 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -269,7 +269,7 @@ class SurveyView(InstitutionMixin, ConceptView): respManager = Responses(self.context) self.teamData = self.getTeamData(respManager) response = Response(self.adapted, None) - groups = self.adapted.getQuestionGroups(self.pesonId) + groups = self.adapted.getQuestionGroups(self.personId) teamValues = response.getTeamResult(groups, self.teamData) for idx, r in enumerate(teamValues): group = r['group'] From e308b8b34f34f660157356c3812a25c6a8b2e8cf Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 09:45:12 +0200 Subject: [PATCH 141/269] add event handling to object removal and log event --- integrator/collection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integrator/collection.py b/integrator/collection.py index 167974b..cc6219b 100644 --- a/integrator/collection.py +++ b/integrator/collection.py @@ -26,6 +26,7 @@ from logging import getLogger import os, re, stat from zope.app.container.interfaces import INameChooser +from zope.app.container.contained import ObjectRemovedEvent from zope.cachedescriptors.property import Lazy from zope import component from zope.component import adapts @@ -134,6 +135,9 @@ class ExternalCollectionAdapter(AdapterBase): def remove(self, obj): del self.resourceManager[getName(obj)] + notify(ObjectRemovedEvent(obj)) + getLogger('loops.integrator.collection').info( + 'object removed: %s.' % getName(obj)) @Lazy def resourceManager(self): From 69e529ce3972624fee701e960182bac2162c51e0 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 10:37:57 +0200 Subject: [PATCH 142/269] log before deleting to be able to detect errors when deleting --- integrator/collection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrator/collection.py b/integrator/collection.py index cc6219b..0471a34 100644 --- a/integrator/collection.py +++ b/integrator/collection.py @@ -134,10 +134,10 @@ class ExternalCollectionAdapter(AdapterBase): self.remove(obj) def remove(self, obj): + getLogger('loops.integrator.collection').info( + 'Removing object: %s.' % getName(obj)) del self.resourceManager[getName(obj)] notify(ObjectRemovedEvent(obj)) - getLogger('loops.integrator.collection').info( - 'object removed: %s.' % getName(obj)) @Lazy def resourceManager(self): From 08d8993e5aa1fc721b80a281fe8bf38ba1eaae28 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 11:16:26 +0200 Subject: [PATCH 143/269] provide commits and logging during update of external collection --- integrator/collection.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/integrator/collection.py b/integrator/collection.py index 0471a34..9b48ac7 100644 --- a/integrator/collection.py +++ b/integrator/collection.py @@ -24,6 +24,7 @@ file system. from datetime import datetime from logging import getLogger import os, re, stat +import transaction from zope.app.container.interfaces import INameChooser from zope.app.container.contained import ObjectRemovedEvent @@ -67,6 +68,7 @@ class ExternalCollectionAdapter(AdapterBase): newResources = None updateMessage = None + logger = getLogger('loops.integrator.collection') def getExclude(self): return getattr(self.context, '_exclude', None) or [] @@ -88,6 +90,7 @@ class ExternalCollectionAdapter(AdapterBase): provider = component.getUtility(IExternalCollectionProvider, name=self.providerName or '') #print '*** old', old, versions, self.lastUpdated + changeCount = 0 for addr, mdate in provider.collect(self): #print '***', addr, mdate if addr in versions: @@ -97,6 +100,7 @@ class ExternalCollectionAdapter(AdapterBase): # for checking for changes... oldFound.append(addr) if self.lastUpdated is None or (mdate and mdate > self.lastUpdated): + changeCount +=1 obj = old[addr] # update settings and regenerate scale variant for media asset adobj = adapted(obj) @@ -111,31 +115,40 @@ class ExternalCollectionAdapter(AdapterBase): self.updateMessage = message # force reindexing notify(ObjectModifiedEvent(obj)) + if changeCount % 100 == 0: + self.logger.info('Updated: %i.' % changeCount) + transaction.commit() else: new.append(addr) + self.logger.info('%i objects updated.' % changeCount) + transaction.commit() if new: self.newResources = provider.createExtFileObjects(self, new) for r in self.newResources: self.context.assignResource(r) + self.logger.info('%i objects created.' % len(new)) + transaction.commit() for addr in old: if str(addr) not in oldFound: # not part of the collection any more # TODO: only remove from collection but keep object? self.remove(old[addr]) + transaction.commit() for r in self.context.getResources(): adobj = adapted(r) if self.metaInfo != adobj.metaInfo and ( not adobj.metaInfo or self.overwriteMetaInfo): adobj.metaInfo = self.metaInfo self.lastUpdated = datetime.today() + self.logger.info('External collection updated.') + transaction.commit() def clear(self): for obj in self.context.getResources(): self.remove(obj) def remove(self, obj): - getLogger('loops.integrator.collection').info( - 'Removing object: %s.' % getName(obj)) + self.logger.info('Removing object: %s.' % getName(obj)) del self.resourceManager[getName(obj)] notify(ObjectRemovedEvent(obj)) From 0ec131e1a8e16e2571941f2e4b38e950b344799a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 11:27:55 +0200 Subject: [PATCH 144/269] fix count-based commit; use set for collection of found objects --- integrator/collection.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integrator/collection.py b/integrator/collection.py index 9b48ac7..4dfc369 100644 --- a/integrator/collection.py +++ b/integrator/collection.py @@ -86,7 +86,7 @@ class ExternalCollectionAdapter(AdapterBase): print '###', vaddr, vobj, vid versions.add(vaddr) new = [] - oldFound = [] + oldFound = set([]) provider = component.getUtility(IExternalCollectionProvider, name=self.providerName or '') #print '*** old', old, versions, self.lastUpdated @@ -98,7 +98,7 @@ class ExternalCollectionAdapter(AdapterBase): if addr in old: # may be it would be better to return a file's hash # for checking for changes... - oldFound.append(addr) + oldFound.add(addr) if self.lastUpdated is None or (mdate and mdate > self.lastUpdated): changeCount +=1 obj = old[addr] @@ -115,9 +115,9 @@ class ExternalCollectionAdapter(AdapterBase): self.updateMessage = message # force reindexing notify(ObjectModifiedEvent(obj)) - if changeCount % 100 == 0: - self.logger.info('Updated: %i.' % changeCount) - transaction.commit() + if changeCount % 100 == 0: + self.logger.info('Updated: %i.' % changeCount) + transaction.commit() else: new.append(addr) self.logger.info('%i objects updated.' % changeCount) From 11123b16fa27e1d23d70329e21965abc3ab39e45 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 13:23:10 +0200 Subject: [PATCH 145/269] more intermediate commits + logging --- integrator/collection.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/integrator/collection.py b/integrator/collection.py index 4dfc369..5f88863 100644 --- a/integrator/collection.py +++ b/integrator/collection.py @@ -53,6 +53,8 @@ from loops.versioning.interfaces import IVersionable TypeInterfaceSourceList.typeInterfaces += (IExternalCollection,) +logger = getLogger('loops.integrator.collection') + class ExternalCollectionAdapter(AdapterBase): """ A concept adapter for accessing an external collection. @@ -68,8 +70,7 @@ class ExternalCollectionAdapter(AdapterBase): newResources = None updateMessage = None - logger = getLogger('loops.integrator.collection') - + def getExclude(self): return getattr(self.context, '_exclude', None) or [] def setExclude(self, value): @@ -115,18 +116,18 @@ class ExternalCollectionAdapter(AdapterBase): self.updateMessage = message # force reindexing notify(ObjectModifiedEvent(obj)) - if changeCount % 100 == 0: - self.logger.info('Updated: %i.' % changeCount) + if changeCount % 10 == 0: + logger.info('Updated: %i.' % changeCount) transaction.commit() else: new.append(addr) - self.logger.info('%i objects updated.' % changeCount) + logger.info('%i objects updated.' % changeCount) transaction.commit() if new: self.newResources = provider.createExtFileObjects(self, new) for r in self.newResources: self.context.assignResource(r) - self.logger.info('%i objects created.' % len(new)) + logger.info('%i objects created.' % len(new)) transaction.commit() for addr in old: if str(addr) not in oldFound: @@ -140,7 +141,7 @@ class ExternalCollectionAdapter(AdapterBase): not adobj.metaInfo or self.overwriteMetaInfo): adobj.metaInfo = self.metaInfo self.lastUpdated = datetime.today() - self.logger.info('External collection updated.') + logger.info('External collection updated.') transaction.commit() def clear(self): @@ -148,7 +149,7 @@ class ExternalCollectionAdapter(AdapterBase): self.remove(obj) def remove(self, obj): - self.logger.info('Removing object: %s.' % getName(obj)) + logger.info('Removing object: %s.' % getName(obj)) del self.resourceManager[getName(obj)] notify(ObjectRemovedEvent(obj)) @@ -204,7 +205,7 @@ class DirectoryCollectionProvider(object): for k, v in self.extFileTypeMapping.items()) container = client.context.getLoopsRoot().getResourceManager() directory = self.getDirectory(client) - for addr in addresses: + for idx, addr in enumerate(addresses): name = self.generateName(container, addr) title = self.generateTitle(addr) contentType = guess_content_type(addr, @@ -217,9 +218,8 @@ class DirectoryCollectionProvider(object): if extFileType is None: extFileType = extFileTypes['image/*'] if extFileType is None: - getLogger('loops.integrator.collection.DirectoryCollectionProvider' - ).warn('No external file type found for %r, ' - 'content type: %r' % (name, contentType)) + logger.warn('No external file type found for %r, ' + 'content type: %r' % (name, contentType)) obj = addAndConfigureObject( container, Resource, name, title=title, @@ -236,6 +236,9 @@ class DirectoryCollectionProvider(object): message = client.updateMessage or u'' message += u'
'.join(adobj.processingErrors) client.updateMessage = message + if idx and idx % 10 == 0: + logger.info('Created: %i.' % idx) + transaction.commit() yield obj def getDirectory(self, client): From bdb10523a4c5f57c70bdf480321f0d4967591bda Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 13:32:46 +0200 Subject: [PATCH 146/269] allow suppression of page output via no_show_page request parameter --- integrator/browser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integrator/browser.py b/integrator/browser.py index aeafa87..e76c012 100644 --- a/integrator/browser.py +++ b/integrator/browser.py @@ -44,5 +44,7 @@ class ExternalCollectionView(ConceptView): cta.update() if cta.updateMessage is not None: self.request.form['message'] = cta.updateMessage + if 'no_show_page' in self.request.form: + return False return True From 085192de21ec20965591ff92c23558e98e311a70 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 13:41:41 +0200 Subject: [PATCH 147/269] put event handling before real deletion --- integrator/collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrator/collection.py b/integrator/collection.py index 5f88863..b33fe4f 100644 --- a/integrator/collection.py +++ b/integrator/collection.py @@ -150,8 +150,8 @@ class ExternalCollectionAdapter(AdapterBase): def remove(self, obj): logger.info('Removing object: %s.' % getName(obj)) - del self.resourceManager[getName(obj)] notify(ObjectRemovedEvent(obj)) + del self.resourceManager[getName(obj)] @Lazy def resourceManager(self): From a45bf52fe7ba62ca229788848a3262fc0305df17 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sat, 25 Jul 2015 13:51:01 +0200 Subject: [PATCH 148/269] check for result of update() function --- integrator/collection_macros.pt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrator/collection_macros.pt b/integrator/collection_macros.pt index 8c3e898..57e5a4b 100644 --- a/integrator/collection_macros.pt +++ b/integrator/collection_macros.pt @@ -2,7 +2,7 @@ + tal:condition="item/update"> Date: Mon, 27 Jul 2015 10:17:28 +0200 Subject: [PATCH 149/269] avoid error on catalog reindex because of missing or corrupt ZIP files --- resource.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resource.py b/resource.py index 143d334..4dcc75f 100644 --- a/resource.py +++ b/resource.py @@ -21,6 +21,7 @@ Definition of the Concept class. """ from cStringIO import StringIO +from logging import getLogger from persistent import Persistent from zope import component, schema from zope.app.container.btree import BTreeContainer @@ -63,6 +64,8 @@ from loops import util from loops.versioning.util import getMaster from loops.view import TargetRelation +logger = getLogger('loops.resource') + _ = MessageFactory('loops') @@ -602,7 +605,12 @@ def transformToText(obj, data=None, contentType=None): if rfa is None: if isinstance(data, unicode): data = data.encode('UTF-8') - return transform(StringIO(data)) + try: + return transform(StringIO(data)) + except: + import traceback + logger.warn(traceback.format_exc()) + return u'' else: return transform(rfa) From 9df059e77a2fe279d66e438bfefb1efc337e83a0 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 4 Aug 2015 12:49:10 +0200 Subject: [PATCH 150/269] avoid indexing error because of corrupt files --- resource.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resource.py b/resource.py index 143d334..4dcc75f 100644 --- a/resource.py +++ b/resource.py @@ -21,6 +21,7 @@ Definition of the Concept class. """ from cStringIO import StringIO +from logging import getLogger from persistent import Persistent from zope import component, schema from zope.app.container.btree import BTreeContainer @@ -63,6 +64,8 @@ from loops import util from loops.versioning.util import getMaster from loops.view import TargetRelation +logger = getLogger('loops.resource') + _ = MessageFactory('loops') @@ -602,7 +605,12 @@ def transformToText(obj, data=None, contentType=None): if rfa is None: if isinstance(data, unicode): data = data.encode('UTF-8') - return transform(StringIO(data)) + try: + return transform(StringIO(data)) + except: + import traceback + logger.warn(traceback.format_exc()) + return u'' else: return transform(rfa) From fc81b49d44cb7724a09acff8dd6182a72a09df5e Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 4 Aug 2015 12:50:11 +0200 Subject: [PATCH 151/269] fix typo (additional email adresses) in send mail action --- organize/browser/party.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/organize/browser/party.py b/organize/browser/party.py index 3f9a78b..afcf656 100644 --- a/organize/browser/party.py +++ b/organize/browser/party.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2011 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -194,7 +194,7 @@ class SendEmail(FormController): subject = form.get('subject') or u'' message = form.get('mailbody') or u'' recipients = form.get('recipients') or [] - recipients += (form.get('addrRecipients') or u'').split('\n') + recipients += (form.get('addrecipients') or u'').split('\n') # TODO: remove duplicates person = getPersonForUser(self.context, self.request) sender = person and adapted(person).email or 'loops@unknown.com' From 48e45941b84ca51ae88986ee20df10211a1d4373 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 13 Aug 2015 16:10:48 +0200 Subject: [PATCH 152/269] provide import file with some stuff missing in older loops sites --- data/loops_std_de.dmp | 2 ++ data/loops_std_update_de.dmp | 9 +++++++++ 2 files changed, 11 insertions(+) create mode 100644 data/loops_std_update_de.dmp diff --git a/data/loops_std_de.dmp b/data/loops_std_de.dmp index 3bb354a..4f5c97c 100644 --- a/data/loops_std_de.dmp +++ b/data/loops_std_de.dmp @@ -1,6 +1,8 @@ # types type(u'query', u'Abfrage', options=u'', typeInterface='loops.expert.concept.IQueryConcept', viewName=u'') +type(u'datatable', u'Datentabelle', options=u'action.portlet:edit_concept', + typeInterface='loops.table.IDataTable', viewName=u'') type(u'task', u'Aufgabe', options=u'', typeInterface='loops.knowledge.interfaces.ITask', viewName=u'') type(u'domain', u'Bereich', options=u'', typeInterface=u'', viewName=u'') diff --git a/data/loops_std_update_de.dmp b/data/loops_std_update_de.dmp new file mode 100644 index 0000000..23c9be3 --- /dev/null +++ b/data/loops_std_update_de.dmp @@ -0,0 +1,9 @@ +# update for old loops sites + +type(u'datatable', u'Datentabelle', options=u'action.portlet:edit_concept', + typeInterface='loops.table.IDataTable', viewName=u'') + +concept(u'issubtype', u'is Subtype', u'predicate') + +child(u'general', u'issubtype', u'datatable') +child(u'system', u'issubtype', u'standard') From 48caf96670db3e0867950da9716ba5c725faf374 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 13 Aug 2015 16:11:53 +0200 Subject: [PATCH 153/269] allow name-based linking e.g. via .loops/resources/... --- browser/node.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/browser/node.py b/browser/node.py index 733b8e6..e5dd4d5 100644 --- a/browser/node.py +++ b/browser/node.py @@ -948,7 +948,8 @@ class NodeTraverser(ItemTraverser): if context.nodeType == 'menu': setViewConfiguration(context, request) if name == '.loops': - return self.context.getLoopsRoot() + name = self.getTargetUid(request) + #return self.context.getLoopsRoot() if name.startswith('.'): name = self.cleanUpTraversalStack(request, name)[1:] target = self.getTarget(name) @@ -975,17 +976,34 @@ class NodeTraverser(ItemTraverser): obj = super(NodeTraverser, self).publishTraverse(request, name) return obj + def getTargetUid(self, request): + parent = self.context.getLoopsRoot() + stack = request._traversal_stack + for i in range(2): + name = stack.pop() + obj = parent.get(name) + if not obj: + return name + parent = obj + return '.' + util.getUidForObject(obj) + def cleanUpTraversalStack(self, request, name): - traversalStack = request._traversal_stack - while traversalStack and traversalStack[0].startswith('.'): + #traversalStack = request._traversal_stack + #while traversalStack and traversalStack[0].startswith('.'): # skip obsolete target references in the url - name = traversalStack.pop(0) + # name = traversalStack.pop(0) traversedNames = request._traversed_names - if traversedNames: - lastTraversed = traversedNames[-1] - if lastTraversed.startswith('.') and lastTraversed != name: + for n in list(traversedNames): + if n.startswith('.'): + # remove obsolete target refs + traversedNames.remove(n) + #if traversedNames: + # lastTraversed = traversedNames[-1] + # if lastTraversed.startswith('.') and lastTraversed != name: # let tag show the current object - traversedNames[-1] = name + # traversedNames[-1] = name + # let tag show the current object + traversedNames.append(name) return name def getTarget(self, name): From f2cf265d0c8cd675771aa92e1de28e97bd9f6440 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 13 Aug 2015 16:12:10 +0200 Subject: [PATCH 154/269] handle boolean values --- external/pyfunc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/external/pyfunc.py b/external/pyfunc.py index 83a0293..b4f7969 100644 --- a/external/pyfunc.py +++ b/external/pyfunc.py @@ -44,11 +44,15 @@ class PyReader(object): class InputProcessor(dict): + _constants = dict(True=True, False=False) + def __init__(self): self.elements = [] - self['__builtins__'] = {} # security! + self['__builtins__'] = dict() # security! def __getitem__(self, key): + if key in self._constants: + return self._constants[key] def factory(*args, **kw): element = elementTypes[key](*args, **kw) if key in toplevelElements: From 0cec511a9fc9aad65f655f7b106e9096e7636d0a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 13 Aug 2015 16:12:53 +0200 Subject: [PATCH 155/269] take personId into account in all relevant places --- knowledge/survey/browser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py index d08cab2..bf2f6cd 100644 --- a/knowledge/survey/browser.py +++ b/knowledge/survey/browser.py @@ -52,7 +52,7 @@ class SurveyView(InstitutionMixin, ConceptView): template = template - adminMaySelectAllInstitutions = False + #adminMaySelectAllInstitutions = False @Lazy def macro(self): @@ -190,7 +190,7 @@ class SurveyView(InstitutionMixin, ConceptView): data = respManager.load(uid, instUid) if data: resp = Response(self.adapted, self.personId) - for qu in self.adapted.questions: + for qu in self.adapted.getQuestions(self.personId): if qu.questionType in (None, 'value_selection'): if qu.uid in data: value = data[qu.uid] @@ -331,7 +331,7 @@ class SurveyView(InstitutionMixin, ConceptView): def check(self, response): errors = [] values = response.values - for qu in self.adapted.questions: + for qu in self.adapted.getQuestions(self.personId): if qu.required and qu not in values: errors.append(dict(uid=qu.uid, text='Please answer the obligatory questions.')) From b8f485d2d35b896fe25a0483943bb0e1c36ebe7b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 17 Aug 2015 10:17:56 +0200 Subject: [PATCH 156/269] allow usage of part of mail form and make it somewhat configurable --- organize/browser/view_macros.pt | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/organize/browser/view_macros.pt b/organize/browser/view_macros.pt index 93ef80e..551933a 100644 --- a/organize/browser/view_macros.pt +++ b/organize/browser/view_macros.pt @@ -123,20 +123,23 @@
Send Link by Email -
-
- +
-
-
-
- + +
+
+
-
-
+ +
+
+
+
From bd1f12ffa0d86361379da0b6614b358dd014fbb2 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 6 Sep 2015 15:08:33 +0200 Subject: [PATCH 157/269] add logging to find hot spots for tuning --- classifier/base.py | 11 +++++------ classifier/browser.py | 11 ++++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/classifier/base.py b/classifier/base.py index 58dcc37..a71d12e 100644 --- a/classifier/base.py +++ b/classifier/base.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -18,8 +18,6 @@ """ Adapters and others classes for analyzing resources. - -$Id$ """ from itertools import tee @@ -41,6 +39,7 @@ from loops.resource import Resource from loops.setup import addAndConfigureObject from loops.type import TypeInterfaceSourceList +logger = getLogger('Classifier') TypeInterfaceSourceList.typeInterfaces += (IClassifier,) @@ -102,15 +101,15 @@ class Classifier(AdapterBase): if resource not in resources: concept.assignResource(resource, predicate) message = u'Assigning: %s %s %s' + self.log(message % (resource.title, predicate.title, concept.title), 5) else: message = u'Already assigned: %s %s %s' - self.log(message % (resource.title, predicate.title, concept.title), 4) + self.log(message % (resource.title, predicate.title, concept.title), 4) def log(self, message, level=5): if level >= self.logLevel: #print 'Classifier %s:' % getName(self.context), message - getLogger('Classifier').info( - u'%s: %s' % (getName(self.context), message)) + logger.info(u'%s: %s' % (getName(self.context), message)) class Extractor(object): diff --git a/classifier/browser.py b/classifier/browser.py index 3ef92a5..d68980d 100644 --- a/classifier/browser.py +++ b/classifier/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# Copyright (c) 2015 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 @@ -18,17 +18,19 @@ """ View class(es) for resource classifiers. - -$Id$ """ +from logging import getLogger from zope import interface, component from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy +from zope.traversing.api import getName from loops.browser.concept import ConceptView from loops.common import adapted +logger = getLogger('ClassifierView') + class ClassifierView(ConceptView): @@ -44,10 +46,12 @@ class ClassifierView(ConceptView): if cta is not None: for r in collectResources(self.context): cta.process(r) + logger.info('Finished processing') return True def collectResources(concept, checkedConcepts=None, result=None): + logger.info('Start collecting resources for %s' % getName(concept)) if result is None: result = [] if checkedConcepts is None: @@ -59,4 +63,5 @@ def collectResources(concept, checkedConcepts=None, result=None): if c not in checkedConcepts: checkedConcepts.append(c) collectResources(c, checkedConcepts, result) + logger.info('Collected %s resources' % len(result)) return result From 773ad4e1eb3bc0235660ddc56ee800083483bdfb Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Sun, 6 Sep 2015 15:40:39 +0200 Subject: [PATCH 158/269] add commits during processing --- classifier/browser.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/classifier/browser.py b/classifier/browser.py index d68980d..e0fc935 100644 --- a/classifier/browser.py +++ b/classifier/browser.py @@ -21,6 +21,7 @@ View class(es) for resource classifiers. """ from logging import getLogger +import transaction from zope import interface, component from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy @@ -44,9 +45,13 @@ class ClassifierView(ConceptView): if 'update' in self.request.form: cta = adapted(self.context) if cta is not None: - for r in collectResources(self.context): + for idx, r in enumerate(collectResources(self.context)): + if idx % 1000 == 0: + logger.info('Committing, resource # %s' % idx) + transaction.commit() cta.process(r) logger.info('Finished processing') + transaction.commit() return True From 4f481ce79b1e64f02d07fcbfba58e519c9302d9b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 22 Sep 2015 07:25:06 +0200 Subject: [PATCH 159/269] handle adding or removal of columns correctly --- table.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/table.py b/table.py index 945ce2b..588bf21 100644 --- a/table.py +++ b/table.py @@ -88,6 +88,13 @@ class DataTable(AdapterBase): if data is None: data = OOBTree() self.context._data = data + reclen = len(self.columns) - 1 + for k, v in data.items(): + v = v[:reclen] + missing = reclen - len(v) + if missing > 0: + v += (missing * [u'']) + data[k] = v return data def setData(self, data): self.context._data = OOBTree(data) @@ -102,6 +109,7 @@ class DataTable(AdapterBase): item[c] = k else: item[c] = v[idx-1] + #item[c] = len(v) > idx and v[idx-1] or u'' result.append(item) return result From 6db71366c636f831dbeccb1ef97b606afe3f53e7 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 22 Sep 2015 07:59:09 +0200 Subject: [PATCH 160/269] send link by mail: translations, allow specifying mail subject in global or type option --- locales/de/LC_MESSAGES/loops.mo | Bin 27817 -> 28106 bytes locales/de/LC_MESSAGES/loops.po | 29 +++++++++++++++++++++++++---- organize/browser/party.py | 4 ++++ organize/browser/view_macros.pt | 8 +++++--- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 48c8944d51b815d1407a3844c69ccc2bc42150dd..0765026519265047291609088485bd12e572cc7b 100644 GIT binary patch delta 10200 zcmZA433yId9>?*Uh$WI(f*`TP8bOfQD%K#D3Q@#fB$5z2iM{%&trWGdt)-})R)ZO1 zi`r?`u2rMe87*2ZT3x1TJKx`X|DMOpeV%_m|8wrS=br7p(QTjkocr9zb352?nZtF_ z$8pNw+l3rws4sb0)jH0Zs*ckc&teS>jc}ZN*v9OL`6+k90@xSxU>XKs1_t3+ERWN% z2yVm@j^lCmlK4|`4g>KjG6v_4l^>xx`~%gYPc^q)5YmAYiXm7B^`f?@{*vr|f6PyL ztd*yrCa?qx@P22tRcu5(xE+h(UT*_d!0und!qk6?>hKb<~WS*!@!#h|JpI~V$!2u|awNcNtMs?g5)!`V_R?I^6 zw+7YEdsfcDQj{;FCj4y;)?a(}Cl%WBz?$w1!%zdMhU)kQ48;W03kRVFGzB$~6;}Tq zs{KjSKtDsZ`yMM`fhf1WI;x-MQLMiXdJ+{HQ6{Q`Sy%~IBV%)ppl0*{wU@c5E%2-5 zz90xyjzB-GgW7`nsKeXb?zb`%un_f09+Ci(RMZ2b?8YS2p3XsaxDfS%Wf+8OQA@eg z>QAC(dI7a^_fXIMYUMvr6AP;CzPJKvLY_J#fg~+adzFZVaRBN?qp%RZidv!hsHIzv z>To~mxpSxvKSwS7L)3(P>$vv=QT>!f^;^@e_c*a68bAlT(Gv?&PPg)C)ZTlrFs??e z%yzrK2ergUQCo8ywNmG?2wp`!cNcXypQ1j?z-X;J`yWiAk(NU}Sj~(^Js4}gh+5L# zsP_F)FU~*>XgF#`valY`w)(@U_9sv?zhK_Na{B%skmz*!$G9`7Vb(>xu(8^O;LxtE9$RpKMzS1$z-gC+mKJy zxr`C$)6j7$VL3F3Kt0qR-;xd2Q|QxsDYhFP2dLV`8#(1JJeZv zf|2_E^E7rx7=`+r>Y+MngZxX(NkHvwB5Ec*P+QOs_2Mz87mi0gHy!ojIj9#evGQ6g zZ$WGVtlh*NNIGf&6VQ7|kv(&^AwLbyB^-!;P2GPZ4#qIb2T=cRxQ1G( zUr__`Y32?v5OwB)o3Z}-o+Io=4Ko@w!-l8_JD|=)chrCeqE;jwwE`nhhcFY>ZjQMe z)qW$Y-DcEtJFpaHH)H*ExIUsn-|H=$gF(&RhAU7bU56UTUet_^qB=Z-dcnu2CBKOA zcoX?mcfwn^GwyVci8z1oMNcpPiuZ7h!gaqgL@ zfodOPHn#fKsKeX=wbK1iE0BU}?-@d(LzRh|!6eiGW?%@eMh$Ea>VZS(J>{t9&Li9F zT*lH^q^0{@b=1V-P%rF^dcG&>kS8PW^EjDSF$MKmEI@6^dMm$=+N)z$e-pKY4^RX7 z0X2}PsF~)XUR1c1yLF{d{Y9bfH$}atGy3WKPbNvFA{{l;^QaE4qaOGIHR30z2mM;R zqgE>jC}$lSUql`tHl2Rv-p7u;!Q#+o4{R zh_OEW4L~OCENIKWlJF|(xgzb{_GMAeS4P!Gq23dVnqZrDtiSfKFBSTQn}qs}--_D% z9MsZZLCxd_>cOwgTr5C2xV_sx3^jpTsIR9bs(ml3ABp~yr=tck-$SC2twKGp8Fg9@ zqdNW={qee$zeEl2zSaA@=-v-NZDk2m$5pXE#-LVcF=|V0n0K)tWzUZ!dcmJoQJ{mn z=fzOJd|{{#>!1eI1l3VI>V8)Yz`>|3$}%UTzKUt6Gqeu%+!oY}_aQ6nadJqs1y@ir zdyM*}^G|Tgl~G&K1a(#t&14LxoQYbwrKsn3pgzCjR{u}bK!39P{vF+4&I(vm-+vs5 zUeFcwU^?o7saC(t+=@ZeA4DznS=8R%#0vNbHSpk0?qA6$tV}rxHNi=!zm6+W?e?Pg z-~XSGL{RZPY5*aLZo@jLB}_m)kb#=v42;CLF&0mu4xLYDcZ*7(mbe_2$4JbJ?NM8l zh(XvLJv!Z~B--1-=4dQJc@hTVTvUfEu`q5#?P<2vA4GL{3iS(k6~pl%>cxd#atB@< z)vhM0zu1>pe=T7=72((iHPcs6^(#O)Y5gtV%QTk&|#>ho`L$D-#}kn zirVuPsI6OzO>sA>KhJk0+WVhSBmTn-OmhDX7lu`+Z-#1@it2DUYGzsHMASfEMV+ZP zPy=}r_1sGH9n=7}BhP!Bdn6jkL(~kOqB{5;y<31Xg!Rv>>*ci@Fk zFAhNsygKTusf9X>jZx3{L=89{HIQ*wg!enMNi_3S*cR7eU;GAjxLWsew;&z0Qu9zV zSc)}pBZlB7sHOfIHGm&bXC>Fn-`l+(jGd?tLytNfLK2E&P$Qg=n&}#>hdEdipJGR> z*vI{!)lnElc@Eowl&paxKg*=WT|qgFH=_1?ODS%1yEF%>#2aaavIpdQS^ zlIX$yxB_+PenIVVE~;JV%kCMdg8Dogqh6F?_5D#RG8VNJ(@+Ck_%iFS7jL0L9qmEQ z;1JfrGpLdNf@=2{>ah9ub62W7YG$=j&o{7g9IBsosIBUT+M0o==ZB%%PxFwd;e6DH zS78&}iCWV8SQ>+vzE&avRc?;a*b6nIxu~ni6n}O zyQmI|CA%GlVMEFl@CEFJI)sZ+138Ksz-81Sy@utmV2b-!F%mN=r(zfU6eF?5K=*4( z!h-t#CzEJovrscxfLhvRsJ%OF_195*b_@0WeuG-V-%#!JKM}rA7}Z}e=D{!w!}6$= zX^i^H649UcJN-%YK!)8IgJmgCL%nD{w#N_fJm#WS>P(vZH{v4d4BSO6?T@JYf1n1G zH{I>O047o{iY+l2J$Xqsk!S|n(GL%xW_kp*)Mrtj&n?v1xQ`mJ?;!WN0MyclVgqc1 zn!qSjyEjq&tuQyB-n(lM>#rrrrb378w0Q;dQT_roqpz+05$Y?*MJ-|R!S2i}qb3l8 z8fcu=cS6myFY0?=hcDuOyYG|1`sb&jaE3d=lBgxChh2YGnt9Ca3Sg| zIf~vDvitW@{r+U;qP}~-A?}KVq6SpSL!z0+VnK|<0+?XsBpgL~0BVMpP#u4X8rVJ5 zi+{s{m~W`NB1KUvRUWm%4b2YdPq{zpaC$OGv=_t8Ow`g&M7?N^xd;O&uR@)XcTq3g zj_PP1>aZR_P3S!8`O8+miJI6Q)W9Dg@9{XlyNMGp%pG|cYJ`!f8Pvr9>|pghP``l5 zR-TM{(M;4=FyDO3?r%fAI2$#`~bBgN6`m668%UD6U)h8#Y?yX_u?)>?{_Ltu>|iBQwaT7v?0n9A9Ih- z*jY*F(l6l=Z{|2T_&%|Y*hRapxEJ^1hlIA}|F1n%tf$RPB7xlVHi@peF3uxtPqZS^ z?B3tWza$nCO{^}L{4w$Dsz-e(qAn3cWj5-6(bKI#cYK-9S9F^C`~;WB=|*yw>^d=; zDB<4lo`Goc-oyvg>zYAck$9W@V`2<(lz2!iCVFu1U)Yw=A=K5EXyDEGJHY*X#HW-$ z!sFicr%;^2NSsJaCcmWy#0%uQ;$0m68hfvaR?qK}t_5+B*lhRSBG)yE z@@rNXMfr^OUl*URGoKqR>}ELSImBvW5OJB%A=IVAmrV2|{AnMFy9pg)UHaZ%v3`n} z+ixoWz2|J$Gc*5H01p&zjg8pX4#KFJ4^PvTcr`Q~%?Q?0JSb9IfZZk{<6 zHxpZF(*^ZIwuab4c^Wa4{6pM_zJ&MuEu!LY#4swl5#i*Wh(p9GVk8knROX&8KNqJj zb z%fgaa3VRSEtbK3t#^mwD5~8EkQ|S^X)ZYhP`h!%B@co3WxNZ}{+}mXLTVqY4EcN^G34UVtJnvFiK%u!+%*Dk-dn;GterfXY z#0hd;Wyo6*vj|^8*C)hmLKDhMT`l4ixvomqraR80oJstHnC(OJY?42TcZe8bEse&a zt`WpLl+Sxh{0TJ+PO`yMVt^{>^*Od;a-n6onha zaBj}PKw>@7gg8mvczkxfPk9QVtEZVn{&(_rR_^S*>;5MLmQr8W>IRa(WbIzj`y+@6 z#5pQYTca`Lf04H%Hd)<|ScCWv@jB6zHmh(aQGw7kfx4B%1@atxozN9Wd{5jYni1*L z#}J;w6b=!yh_l3bdw|q=lXyT3rLGQU+P$UZ8!g{yzDAv{U>9eU)z`MN7wQHPg{-ct z_P?z)T&5eAziYOn;Q{h-_CP(eAy%gT4LpNqiCDYWjQk(O@07nKh7(DYA zr#?l0ODmE*C3O9;q6lE`b!^Iz zw1HXKxzUI6=6}{~Z^IitK?xZnhK8q1438g9yCpLlMfj(UNDWU@DSLaPhJk*KqGDpQ z@5TrD2KPwI$VwZNM$=L0$2W}_lr|>&aodofz;0<1vYeJ_S!2@D)3fs>Rn1@UxfV70 ihWYyGB}9NlTP(FpV@WIawWzlKwbQYOX((z}do|Wk zTGTp5M^qoB!_-oy?bzDdI(R;Qqn0QKwK8v;(@_oOVliBXn$b49zYBxOA4c6jjasS8 zsP=wCb?jf$aY|ts^r&Jz5}ILiRK?Et5@ur=oPou073#s=sD{s=8oZ9$ibtsSigHlY zP7Ep^kEO8%Mqnmt%SObq{@U}26lkV%Py<w_^k!OEAR+S!5S@pvuPUn9Cfff{&(l`tgEJ?(W-Gw6-l%VDT37>nw_WBDcM zM}9R1;|A2>-C_54nFmo5K8{83TQ#5uuG)=XP<#3ZszKk{Zij&wN(XQ3uE#_mr=hja|4LHSYj}^%G#u&Z-?~u?8mYeHP9d0#0Lv_3dHPAzrKZP2= zH>l??q0YiJ)J&hE23jcI9bh1alP`y=R|`GaB+^I}!_BBYJb+q(0@Tc|+WiNpr3|X? z&Zr`4Z{tv}V=_kKYgV3P<>OF?Z60bRmZ1*uruwYEW^jZ8&EOPjh6U(dV$@z;K|Sy@ zY9;(wXBmcCsnV#1%UeDc*#)OAw!^*{f!nbP9!B+dD}nXb$nM&Wr>G^W)WEHfU?!vL zwLmRxN7O*Op*re=n#ckyfqAIIybbk>bRWjyw^$wh68ZjPoQFg;61}l9PQ`M#5!Jy_ z)K;9a{AJY0Z(8{s)PSDbegB5;N`#@_o+!&V#Ny;zVk_*4S~<^Z657KJs1A2yBz}Qf z(sNh>f3W)xQ7hpza`!wG^;{LymejUAR-l%0 zEo$#KqXxLg^2bmsRDkOEHmd$ZRJ~`&?;$7jWygueMyS2-fjad=P%AwhOX>aJOhOGE zK^>0MsFB`B4d@|iAWu;<3{GD`z5V3nyWK z-v3!7wB++q9j`@oxDoZ>E>y?+QHScN<^OK^i>R%-fgA(pSF`3TZoV&SAbF?(Y(gEz zt*F1IoJ%BlwVguAj?*6#u>&r}a`-dq?}5O^?n>1}4WvG5fK5<`s|D({?P2AaW`EQK z2cn*P4|N7+HfH@bqQw+wN%BxDuo89X)>*~<<_T2&^Qd|kP|y7XOXCgHS$c+gEyJ3y zy4Vy|?<8uVXHf(Bu?g$1y}w6+8hnE4;5ll^eVg*iU4h~rP2~_>dsI9t*W$||piCQGWQ`|p~8>0?SChCD~b115OEb27Bhg$0SsF^K7 z)nA6Hw+=OdEvSL*#*%m%HLz=_`kvb)G^2;82cIIV=lC^qUzb$WgS}BR8;$C4GOB?O zP=_lQ)!{nJZ%4fqhfrHmVEL=4t@_n1_c$S`?h;0!1`>lBNHx?q0%iva2gU3+= zEif;jI=GA)_)XLb-9`=Q0qV5>fqE{eg}ZfO7()L}1PN8Fh7@9Q&9t&gE|9?Q0;HB`=6mlBie5_4w)y>kMgsqrTP|iYHy*=zys62 zrJFBqRzlT}M-99M>b>uPT7hiTz(%45HlZc!??+-11xY@9laaQaLurn~H^~WT)E+0G8g7YM z*b%idJ5XEk!1QbF9?A#|ro6o6t6?Ddc+^{yglaDXHK1Ooc0AcuVU$&vfm-TZb1CX= zScPivAnL(mSQx)St=JXR(%(nTEWC~Tg{zLrH%Ha$g*qd{Ts_YFB%&!;h+4XQ)B~qb zuiaHE{|{;=k!{`k)v+S^6jXzKP#ugyJvR+i|6?oPX&y&S;39h8|63%qw~sLfi?(w| zUJt90Z;O?23~Gi;P`?%TpuXYfumavj)emj&4xj<5UIuCf2czoGL``rF*3kREp9H_M zog1h_7oFkmQ37g-8>42Lj{cZ~+M;0?if^G#_Y~CL&M@br&e9SL!A+?4_Fz#wj2`W2 zf!(->YVZbnztI>?KBR-&aU5#o^-=ZOpc?FfTET3L#tEpIeq`mlQCo8wwZ&Jg{6Poa zf9+97M|ZDcP?Wl&2p*~dS&5Pz0tV8*AjKJcZ-2qfXZBer2n`063-BImk zqgHN+heRZaaj2R73AM*-Q1A0b^u>HsgS)XP9>B)Qlc)g)c5&;6q9#%dwIVf8Ta}Ds zu^VavXRrkQJC{jBQt%t<^$P0h?sZjELrqb8+zmCefmjm9qwdc`-QR$E-S(k6K94%2 zw@@qa05#(>-P{$6#z_7CuTDZUO-8*YDX7EO4%NUQ)QHEUI+}?(JWEkC-;Awr2lmFt zsI%0yySwD$QCqYEHG%aQi@UI--v1v+XlWmz2H?}fJse?X8PxsC*dF6h4Ze$7+Nr1k zevF#wc1*wmtc8I+-Tw+s!0O~DV;pWqPbCuHkrxFu@l z?NDc0hw+`;iwg=ikeU&>iNc*tiKA_}NjZlYeBx)clPy^V3I*dE90{#n=FsLu9s`tM+iB1&k z!5Ub&pZnS*pq9KRYG7}mIv$2v(lO|LoveHps{Ve|YkCZ|q8Cv0uAnd8Mzwbj3+erT zLZTc6&rmZi+uuDbwNWe41oc3=m3PGQEF3dLLJ=147`h}nDT}@<0+^a%tAlRL(OzKYN&?$Ffc!z!jE-9Q*QodY66$sP6*coh z1KfcWLk%<ap+N8VL~McFjzJPlE25RPaQ7iS#Lqbbja*%6H3?|8HE83c!P)pk#)lrT)0<}fs zQDq^j2dW_dU)`66$y%YJ?x5I^K%)@BkLaJE$f08{+P5B&xwwR0kbU zd)*!N{99NAr(p;#z{0o!eeg7~PVfJAavHg=Ed3x)UruGbj)|&m*I^uB`7OAdxcK5G zMv&h@R3p;K&mr=N81iGW3N9ve4b%_4|Jf8&QHE;-Y2F&=2==gwDj#X-Pw|!t{_A>; zvK2%WQG?jcy-x^V(zG+ZEN;=2#8RX|# zy&ouRZ{_1~vX$>N?UwU_<*QTh4}x!_(~`0VxP~Yo(ysACzXHw^3kh9qsdNX26OD-l zLn0oODyn!ZE1pAZcCuUA#Y6QPz=2CHJ+J-;f%)K5rSs z`&8&c>?0kCF*uSqMtTWRgY+C6ihstZn2E`#>n|?Oe#&&|SE#N}EI%3>lHW=6Bb`rp z^u;+&Y$rBY#Xm?tAatE07FoIS&j@{$LMi(aClk%>emVS$r8`qznDh*M3wz^xgsv5Y zKG{|EH)sGiHxtJws6(U>FOi>)x>o+*doqdAN0hX~Mnpx@pQEmgL?6=s!3d%xae!z@ z*?J5kUR?Ux>jSGRmI$HFL~G}-vKDkeEW`6NLy}fzqx)2 znvm{B=D1ttamtX$BNkCu6_4Ua?oDSC{?(rBN_rac8~J|3L1Ge7jd*d*=6)M8C9peQ zCH4{%30)J2y4-6|3@2vk{5K=9lp8aNC_B_viwy1(CRHTb!#B$ zSmKG*Yk^yd1C+h}U-fjmjfaAtNxVz6RteV*7v~6mMZ{63EdH5DCY?&OAl@MEQdSH* zViM}=fIV?A##>n}@_mRm$=^or)hv2w?&+#`^9!o&FPy(NexHASPNQ=^x$To;@+TxU q2=PmdtDBggpBCzye=fa5X#VeAss!d&$t>%e+aoJJe@Rxou>S?KQ7;q# diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index bfa8d88..d938cc9 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: 0.13.1\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2015-06-06 12:00 CET\n" +"PO-Revision-Date: 2015-09-22 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -597,9 +597,6 @@ msgstr "Informationen über dieses Objekt" msgid "Information about this object." msgstr "Informationen über dieses Objekt." -msgid "Send a link to this object by email." -msgstr "Einen Link zu diesem Objekt per E-Mail versenden." - msgid "Edit with external editor." msgstr "Mit 'External Editor' bearbeiten." @@ -1413,3 +1410,27 @@ msgstr "Zeitraum" msgid "Technology" msgstr "Technik" + +# send mail + +msgid "Send a link to this object by email." +msgstr "Einen Link zu diesem Objekt per E-Mail versenden." + +msgid "Send Link by Email" +msgstr "Link per E-Mail versenden" + +msgid "Mail Subject" +msgstr "Betreff" + +msgid "Mail Body" +msgstr "Text" + +msgid "Recipients" +msgstr "Empfänger" + +msgid "Additional Recipients" +msgstr "Weitere Empfänger" + +msgid "Send email" +msgstr "E-Mail senden" + diff --git a/organize/browser/party.py b/organize/browser/party.py index afcf656..ab716b3 100644 --- a/organize/browser/party.py +++ b/organize/browser/party.py @@ -175,6 +175,10 @@ class SendEmailForm(NodeView): @Lazy def subject(self): + optionKey = 'organize.sendmail_subject' + option = self.globalOptions(optionKey) or self.typeOptions(optionKey) + if option: + return option[0] menu = self.context.getMenu() zdc = IZopeDublinCore(menu) zdc.languageInfo = self.languageInfo diff --git a/organize/browser/view_macros.pt b/organize/browser/view_macros.pt index 551933a..bee5d4c 100644 --- a/organize/browser/view_macros.pt +++ b/organize/browser/view_macros.pt @@ -125,14 +125,15 @@
- +
- +