From 70e53daadf088c04fc43677574f263353719263b Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 15 Jun 2015 07:33:27 +0200 Subject: [PATCH 01/23] 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 02/23] 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 9b74079bcaf3b5f9df66812865fdf778a9703610 Mon Sep 17 00:00:00 2001 From: hplattner Date: Thu, 25 Jun 2015 16:21:22 +0200 Subject: [PATCH 03/23] 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 04/23] 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 05/23] 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 06/23] 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 07/23] 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 08/23] 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 09/23] 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 10/23] 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 11/23] 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 12/23] 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 13/23] 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 14/23] 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 15/23] 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 16/23] 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: Tue, 4 Aug 2015 12:49:10 +0200 Subject: [PATCH 17/23] 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 18/23] 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 19/23] 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 20/23] 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 21/23] 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 22/23] 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 23/23] 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 -
-
- +
-
-
-
- + +
+
+
-
- + +
+
+ +