merge branch master

This commit is contained in:
Helmut Merz 2015-08-29 11:12:58 +02:00
commit 07bb68ae9d
15 changed files with 152 additions and 57 deletions

View file

@ -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

View file

@ -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):

View file

@ -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'')

View 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
View file

@ -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:

View file

@ -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

View file

@ -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,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,
@ -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):

View file

@ -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"

View file

@ -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):

View file

@ -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

View file

@ -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'

View file

@ -123,20 +123,23 @@
<div class="heading">
<span i18n:translate="">Send Link by Email</span> -
<span tal:content="view/target/title"></span></div>
<div>
<label i18n:translate="" for="subject">Subject</label>
<metal:content define-macro="mail_content">
<div>
<input name="subject" id="subject" style="width: 60em"
dojoType="dijit.form.ValidationTextBox" required
tal:attributes="value view/subject" /></div>
</div>
<div>
<label i18n:translate="" for="mailbody">Mail Body</label>
<label i18n:translate="" for="subject">Subject</label>
<div>
<input name="subject" id="subject" style="width: 60em"
dojoType="dijit.form.ValidationTextBox" required
tal:attributes="value view/subject" /></div>
</div>
<div>
<textarea name="mailbody" cols="80" rows="4" id="mailbody"
dojoType="dijit.form.SimpleTextarea" style="width: 60em"
tal:content="view/mailBody"></textarea></div>
</div>
<label i18n:translate="" for="mailbody">Mail Body</label>
<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">

View file

@ -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'.
"""

View file

@ -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>

View file

@ -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))