merge branch master
This commit is contained in:
commit
07bb68ae9d
15 changed files with 152 additions and 57 deletions
|
@ -1066,6 +1066,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
|
||||
|
|
|
@ -954,7 +954,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)
|
||||
|
@ -981,17 +982,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 <base .../> tag show the current object
|
||||
traversedNames[-1] = name
|
||||
# traversedNames[-1] = name
|
||||
# let <base .../> tag show the current object
|
||||
traversedNames.append(name)
|
||||
return name
|
||||
|
||||
def getTarget(self, name):
|
||||
|
|
|
@ -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'')
|
||||
|
|
9
data/loops_std_update_de.dmp
Normal file
9
data/loops_std_update_de.dmp
Normal file
|
@ -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')
|
6
external/pyfunc.py
vendored
6
external/pyfunc.py
vendored
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -24,8 +24,10 @@ 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
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope import component
|
||||
from zope.component import adapts
|
||||
|
@ -51,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.
|
||||
|
@ -83,10 +87,11 @@ 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
|
||||
changeCount = 0
|
||||
for addr, mdate in provider.collect(self):
|
||||
#print '***', addr, mdate
|
||||
if addr in versions:
|
||||
|
@ -94,8 +99,9 @@ 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]
|
||||
# update settings and regenerate scale variant for media asset
|
||||
adobj = adapted(obj)
|
||||
|
@ -110,29 +116,41 @@ class ExternalCollectionAdapter(AdapterBase):
|
|||
self.updateMessage = message
|
||||
# force reindexing
|
||||
notify(ObjectModifiedEvent(obj))
|
||||
if changeCount % 10 == 0:
|
||||
logger.info('Updated: %i.' % changeCount)
|
||||
transaction.commit()
|
||||
else:
|
||||
new.append(addr)
|
||||
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)
|
||||
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()
|
||||
logger.info('External collection updated.')
|
||||
transaction.commit()
|
||||
|
||||
def clear(self):
|
||||
for obj in self.context.getResources():
|
||||
self.remove(obj)
|
||||
|
||||
def remove(self, obj):
|
||||
logger.info('Removing object: %s.' % getName(obj))
|
||||
notify(ObjectRemovedEvent(obj))
|
||||
del self.resourceManager[getName(obj)]
|
||||
|
||||
@Lazy
|
||||
|
@ -187,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,
|
||||
|
@ -200,8 +218,7 @@ 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, '
|
||||
logger.warn('No external file type found for %r, '
|
||||
'content type: %r' % (name, contentType))
|
||||
obj = addAndConfigureObject(
|
||||
container, Resource, name,
|
||||
|
@ -219,6 +236,9 @@ class DirectoryCollectionProvider(object):
|
|||
message = client.updateMessage or u''
|
||||
message += u'<br />'.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):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
<metal:block define-macro="render_collection"
|
||||
tal:define="dummy item/update">
|
||||
tal:condition="item/update">
|
||||
|
||||
<metal:block use-macro="view/concept_macros/conceptdata">
|
||||
<metal:fill tal:condition="item/editable"
|
||||
|
|
|
@ -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,11 +46,17 @@ class Questionnaire(AdapterBase, Questionnaire):
|
|||
|
||||
@property
|
||||
def questionGroups(self):
|
||||
return self.getQuestionGroups()
|
||||
|
||||
def getQuestionGroups(self, personId=None):
|
||||
return [adapted(c) for c in self.context.getChildren()]
|
||||
|
||||
@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
|
||||
|
@ -65,12 +71,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):
|
||||
|
|
|
@ -52,7 +52,7 @@ class SurveyView(InstitutionMixin, ConceptView):
|
|||
|
||||
template = template
|
||||
|
||||
adminMaySelectAllInstitutions = False
|
||||
#adminMaySelectAllInstitutions = False
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
|
@ -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 = []
|
||||
|
@ -118,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
|
||||
|
@ -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,8 +189,8 @@ class SurveyView(InstitutionMixin, ConceptView):
|
|||
uid = self.getUidForObject(c)
|
||||
data = respManager.load(uid, instUid)
|
||||
if data:
|
||||
resp = Response(self.adapted, None)
|
||||
for qu in self.adapted.questions:
|
||||
resp = Response(self.adapted, self.personId)
|
||||
for qu in self.adapted.getQuestions(self.personId):
|
||||
if qu.questionType in (None, 'value_selection'):
|
||||
if qu.uid in data:
|
||||
value = data[qu.uid]
|
||||
|
@ -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.personId)
|
||||
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
|
||||
|
@ -327,13 +331,13 @@ 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.'))
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
@ -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,12 +185,16 @@ 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''
|
||||
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'
|
||||
|
|
|
@ -123,6 +123,7 @@
|
|||
<div class="heading">
|
||||
<span i18n:translate="">Send Link by Email</span> -
|
||||
<span tal:content="view/target/title"></span></div>
|
||||
<metal:content define-macro="mail_content">
|
||||
<div>
|
||||
<label i18n:translate="" for="subject">Subject</label>
|
||||
<div>
|
||||
|
@ -135,8 +136,10 @@
|
|||
<div>
|
||||
<textarea name="mailbody" cols="80" rows="4" id="mailbody"
|
||||
dojoType="dijit.form.SimpleTextarea" style="width: 60em"
|
||||
tal:attributes="rows view/contentHeight|string:4"
|
||||
tal:content="view/mailBody"></textarea></div>
|
||||
</div>
|
||||
</metal:content>
|
||||
<div>
|
||||
<label i18n:translate="">Recipients</label>
|
||||
<div tal:repeat="member view/members">
|
||||
|
|
|
@ -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
|
||||
|
@ -60,10 +61,10 @@ def getPersonForUser(context, request=None, principal=None):
|
|||
if context is None:
|
||||
return None
|
||||
if principal is None:
|
||||
if request is None:
|
||||
principal = getCurrentPrincipal()
|
||||
else:
|
||||
if request is not None:
|
||||
principal = getattr(request, 'principal', None)
|
||||
else:
|
||||
principal = getPrincipal(context)
|
||||
if principal is None:
|
||||
return None
|
||||
loops = context.getLoopsRoot()
|
||||
|
@ -78,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'.
|
||||
"""
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
<tal:version tal:condition="view/useVersioning">
|
||||
<td class="center"
|
||||
tal:define="version object/version">
|
||||
<a tal:omit-tag="python: version == '1.1'"
|
||||
<a tal:omit-tag="python: version in ('1.1', '1', '')"
|
||||
tal:attributes="href string:$url?loops.viewName=listversions"
|
||||
tal:content="version">1.1</a>
|
||||
</td>
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Reference in a new issue