merge branch master
This commit is contained in:
commit
b5ab23a90b
82 changed files with 2000 additions and 492 deletions
|
@ -2,8 +2,6 @@
|
||||||
loops - Linked Objects for Organization and Processing Services
|
loops - Linked Objects for Organization and Processing Services
|
||||||
===============================================================
|
===============================================================
|
||||||
|
|
||||||
($Id$)
|
|
||||||
|
|
||||||
The loops platform consists up of three basic types of objects:
|
The loops platform consists up of three basic types of objects:
|
||||||
|
|
||||||
(1) concepts: simple interconnected objects usually representing
|
(1) concepts: simple interconnected objects usually representing
|
||||||
|
@ -612,7 +610,7 @@ Actions
|
||||||
>>> view.controller = Controller(view, request)
|
>>> view.controller = Controller(view, request)
|
||||||
>>> #view.setupController()
|
>>> #view.setupController()
|
||||||
|
|
||||||
>>> actions = view.getActions('portlet')
|
>>> actions = view.getAllowedActions('portlet')
|
||||||
>>> len(actions)
|
>>> len(actions)
|
||||||
2
|
2
|
||||||
|
|
||||||
|
@ -849,7 +847,7 @@ In order to provide suitable links for viewing or editing a target you may
|
||||||
ask a view which view and edit actions it supports. We directly use the
|
ask a view which view and edit actions it supports. We directly use the
|
||||||
target object's view here:
|
target object's view here:
|
||||||
|
|
||||||
>>> actions = view.virtualTarget.getActions('object', page=view)
|
>>> actions = view.virtualTarget.getAllowedActions('object', page=view)
|
||||||
>>> #actions[0].url
|
>>> #actions[0].url
|
||||||
|
|
||||||
'http://127.0.0.1/loops/views/m1/m11/m111/.target23'
|
'http://127.0.0.1/loops/views/m1/m11/m111/.target23'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -129,6 +129,7 @@ actions.register('edit_object', 'portlet', DialogAction,
|
||||||
viewName='edit_object.html',
|
viewName='edit_object.html',
|
||||||
dialogName='edit',
|
dialogName='edit',
|
||||||
prerequisites=['registerDojoEditor'],
|
prerequisites=['registerDojoEditor'],
|
||||||
|
permission='zope.ManageContent',
|
||||||
)
|
)
|
||||||
|
|
||||||
actions.register('edit_concept', 'portlet', DialogAction,
|
actions.register('edit_concept', 'portlet', DialogAction,
|
||||||
|
@ -137,6 +138,7 @@ actions.register('edit_concept', 'portlet', DialogAction,
|
||||||
viewName='edit_concept.html',
|
viewName='edit_concept.html',
|
||||||
dialogName='edit',
|
dialogName='edit',
|
||||||
prerequisites=['registerDojoEditor'],
|
prerequisites=['registerDojoEditor'],
|
||||||
|
permission='zope.ManageContent',
|
||||||
)
|
)
|
||||||
|
|
||||||
actions.register('create_concept', 'portlet', DialogAction,
|
actions.register('create_concept', 'portlet', DialogAction,
|
||||||
|
@ -146,6 +148,7 @@ actions.register('create_concept', 'portlet', DialogAction,
|
||||||
dialogName='createConcept',
|
dialogName='createConcept',
|
||||||
qualifier='create_concept',
|
qualifier='create_concept',
|
||||||
innerForm='inner_concept_form.html',
|
innerForm='inner_concept_form.html',
|
||||||
|
permission='loops.AssignAsParent',
|
||||||
)
|
)
|
||||||
|
|
||||||
actions.register('create_subtype', 'portlet', DialogAction,
|
actions.register('create_subtype', 'portlet', DialogAction,
|
||||||
|
@ -155,4 +158,5 @@ actions.register('create_subtype', 'portlet', DialogAction,
|
||||||
dialogName='createConcept',
|
dialogName='createConcept',
|
||||||
qualifier='subtype',
|
qualifier='subtype',
|
||||||
innerForm='inner_concept_form.html',
|
innerForm='inner_concept_form.html',
|
||||||
|
permission='loops.AssignAsParent',
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -45,7 +45,7 @@ from zope.publisher.browser import applySkin
|
||||||
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
|
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
|
||||||
from zope import schema
|
from zope import schema
|
||||||
from zope.schema.vocabulary import SimpleTerm
|
from zope.schema.vocabulary import SimpleTerm
|
||||||
from zope.security import canAccess, checkPermission
|
from zope.security import canAccess
|
||||||
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
||||||
from zope.security.proxy import removeSecurityProxy
|
from zope.security.proxy import removeSecurityProxy
|
||||||
from zope.traversing.browser import absoluteURL
|
from zope.traversing.browser import absoluteURL
|
||||||
|
@ -54,10 +54,12 @@ from zope.traversing.api import getName, getParent
|
||||||
from cybertools.ajax.dojo import dojoMacroTemplate
|
from cybertools.ajax.dojo import dojoMacroTemplate
|
||||||
from cybertools.browser.view import GenericView
|
from cybertools.browser.view import GenericView
|
||||||
from cybertools.meta.interfaces import IOptions
|
from cybertools.meta.interfaces import IOptions
|
||||||
|
from cybertools.meta.element import Element
|
||||||
from cybertools.relation.interfaces import IRelationRegistry
|
from cybertools.relation.interfaces import IRelationRegistry
|
||||||
from cybertools.stateful.interfaces import IStateful
|
from cybertools.stateful.interfaces import IStateful
|
||||||
from cybertools.text import mimetypes
|
from cybertools.text import mimetypes
|
||||||
from cybertools.typology.interfaces import IType, ITypeManager
|
from cybertools.typology.interfaces import IType, ITypeManager
|
||||||
|
from cybertools.util.date import toLocalTime
|
||||||
from cybertools.util.jeep import Jeep
|
from cybertools.util.jeep import Jeep
|
||||||
from loops.browser.util import normalizeForUrl
|
from loops.browser.util import normalizeForUrl
|
||||||
from loops.common import adapted, baseObject
|
from loops.common import adapted, baseObject
|
||||||
|
@ -66,6 +68,7 @@ from loops.i18n.browser import I18NView
|
||||||
from loops.interfaces import IResource, IView, INode, ITypeConcept
|
from loops.interfaces import IResource, IView, INode, ITypeConcept
|
||||||
from loops.organize.tracking import access
|
from loops.organize.tracking import access
|
||||||
from loops.resource import Resource
|
from loops.resource import Resource
|
||||||
|
from loops.security.common import checkPermission
|
||||||
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
||||||
from loops.type import ITypeConcept
|
from loops.type import ITypeConcept
|
||||||
from loops import util
|
from loops import util
|
||||||
|
@ -131,6 +134,7 @@ class BaseView(GenericView, I18NView):
|
||||||
|
|
||||||
actions = {}
|
actions = {}
|
||||||
portlet_actions = []
|
portlet_actions = []
|
||||||
|
parts = ()
|
||||||
icon = None
|
icon = None
|
||||||
modeName = 'view'
|
modeName = 'view'
|
||||||
isToplevel = False
|
isToplevel = False
|
||||||
|
@ -184,6 +188,15 @@ class BaseView(GenericView, I18NView):
|
||||||
return '%s/.%s-%s' % (baseUrl, targetId, normalizeForUrl(title))
|
return '%s/.%s-%s' % (baseUrl, targetId, normalizeForUrl(title))
|
||||||
return '%s/.%s' % (baseUrl, targetId)
|
return '%s/.%s' % (baseUrl, targetId)
|
||||||
|
|
||||||
|
def filterInput(self):
|
||||||
|
result = []
|
||||||
|
for name in self.getOptions('filter_input'):
|
||||||
|
view = component.queryMultiAdapter(
|
||||||
|
(self.context, self.request), name='filter_input.' + name)
|
||||||
|
if view is not None:
|
||||||
|
result.append(view)
|
||||||
|
return result
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def principalId(self):
|
def principalId(self):
|
||||||
principal = self.request.principal
|
principal = self.request.principal
|
||||||
|
@ -258,6 +271,8 @@ class BaseView(GenericView, I18NView):
|
||||||
d = dc.modified or dc.created
|
d = dc.modified or dc.created
|
||||||
if isinstance(d, str):
|
if isinstance(d, str):
|
||||||
d = datetime(*(strptime(d, '%Y-%m-%dT%H:%M')[:6]))
|
d = datetime(*(strptime(d, '%Y-%m-%dT%H:%M')[:6]))
|
||||||
|
else:
|
||||||
|
d = toLocalTime(d)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -486,7 +501,7 @@ class BaseView(GenericView, I18NView):
|
||||||
if text is None:
|
if text is None:
|
||||||
return u''
|
return u''
|
||||||
htmlPattern = re.compile(r'<(.+)>.+</\1>')
|
htmlPattern = re.compile(r'<(.+)>.+</\1>')
|
||||||
if htmlPattern.search(text):
|
if '<br />' in text or htmlPattern.search(text):
|
||||||
return text
|
return text
|
||||||
return self.renderText(text, 'text/restructured')
|
return self.renderText(text, 'text/restructured')
|
||||||
|
|
||||||
|
@ -565,16 +580,29 @@ class BaseView(GenericView, I18NView):
|
||||||
return DummyOptions()
|
return DummyOptions()
|
||||||
return component.queryAdapter(self.adapted, IOptions) or DummyOptions()
|
return component.queryAdapter(self.adapted, IOptions) or DummyOptions()
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def globalOptions(self):
|
|
||||||
return IOptions(self.loopsRoot)
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def typeOptions(self):
|
def typeOptions(self):
|
||||||
if self.typeProvider is None:
|
if self.typeProvider is None:
|
||||||
return DummyOptions()
|
return DummyOptions()
|
||||||
return IOptions(adapted(self.typeProvider))
|
return IOptions(adapted(self.typeProvider))
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def globalOptions(self):
|
||||||
|
return IOptions(self.loopsRoot)
|
||||||
|
|
||||||
|
def getOptions(self, keys):
|
||||||
|
for opt in (self.options, self.typeOptions, self.globalOptions):
|
||||||
|
if isinstance(opt, DummyOptions):
|
||||||
|
continue
|
||||||
|
#import pdb; pdb.set_trace()
|
||||||
|
v = opt
|
||||||
|
for key in keys.split('.'):
|
||||||
|
if isinstance(v, list):
|
||||||
|
break
|
||||||
|
v = getattr(v, key)
|
||||||
|
if not isinstance(v, DummyOptions):
|
||||||
|
return v
|
||||||
|
|
||||||
def getPredicateOptions(self, relation):
|
def getPredicateOptions(self, relation):
|
||||||
return IOptions(adapted(relation.predicate), None) or DummyOptions()
|
return IOptions(adapted(relation.predicate), None) or DummyOptions()
|
||||||
|
|
||||||
|
@ -648,7 +676,10 @@ class BaseView(GenericView, I18NView):
|
||||||
|
|
||||||
# states
|
# states
|
||||||
|
|
||||||
viewStatesPermission = 'zope.ManageContent'
|
@Lazy
|
||||||
|
def viewStatesPermission(self):
|
||||||
|
opt = self.globalOptions('organize.show_states')
|
||||||
|
return opt and opt[0] or 'zope.ManageContent'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def states(self):
|
def states(self):
|
||||||
|
@ -685,6 +716,16 @@ class BaseView(GenericView, I18NView):
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def getAllowedActions(self, category='object', page=None, target=None):
|
||||||
|
result = []
|
||||||
|
for act in self.getActions(category, page=page, target=target):
|
||||||
|
if act.permission is not None:
|
||||||
|
ctx = (target is not None and target.context) or self.context
|
||||||
|
if not checkPermission(act.permission, ctx):
|
||||||
|
continue
|
||||||
|
result.append(act)
|
||||||
|
return result
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def showObjectActions(self):
|
def showObjectActions(self):
|
||||||
return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
||||||
|
@ -822,6 +863,7 @@ class BaseView(GenericView, I18NView):
|
||||||
|
|
||||||
def registerDojoFormAll(self):
|
def registerDojoFormAll(self):
|
||||||
self.registerDojo()
|
self.registerDojo()
|
||||||
|
self.registerDojoEditor()
|
||||||
cm = self.controller.macros
|
cm = self.controller.macros
|
||||||
jsCall = ('dojo.require("dijit.form.Form"); '
|
jsCall = ('dojo.require("dijit.form.Form"); '
|
||||||
'dojo.require("dijit.form.DateTextBox"); '
|
'dojo.require("dijit.form.DateTextBox"); '
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -200,16 +200,22 @@ class BaseRelationView(BaseView):
|
||||||
class ConceptView(BaseView):
|
class ConceptView(BaseView):
|
||||||
|
|
||||||
template = concept_macros
|
template = concept_macros
|
||||||
|
templateName = 'concept.standard'
|
||||||
|
macroName = 'conceptdata'
|
||||||
|
partPrefix = 'part_'
|
||||||
|
defaultParts = ('title', 'fields',
|
||||||
|
'children', 'resources', 'workitems', 'comments',)
|
||||||
|
|
||||||
def childViewFactory(self, *args, **kw):
|
def childViewFactory(self, *args, **kw):
|
||||||
return ConceptRelationView(*args, **kw)
|
return ConceptRelationView(*args, **kw)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macros(self):
|
||||||
return self.template.macros['conceptdata']
|
return self.controller.getTemplateMacros(self.templateName, self.template)
|
||||||
|
|
||||||
#def __init__(self, context, request):
|
@property
|
||||||
# super(ConceptView, self).__init__(context, request)
|
def macro(self):
|
||||||
|
return self.macros[self.macroName]
|
||||||
|
|
||||||
def setupController(self):
|
def setupController(self):
|
||||||
cont = self.controller
|
cont = self.controller
|
||||||
|
@ -222,6 +228,24 @@ class ConceptView(BaseView):
|
||||||
subMacro=concept_macros.macros['parents'],
|
subMacro=concept_macros.macros['parents'],
|
||||||
priority=20, info=self)
|
priority=20, info=self)
|
||||||
|
|
||||||
|
def getParts(self):
|
||||||
|
parts = (self.params.get('parts') or []) # deprecated!
|
||||||
|
if not parts:
|
||||||
|
parts = (self.options('parts') or self.typeOptions('parts') or
|
||||||
|
self.defaultParts)
|
||||||
|
return self.getPartViews(parts)
|
||||||
|
|
||||||
|
def getPartViews(self, parts):
|
||||||
|
result = []
|
||||||
|
for p in parts:
|
||||||
|
viewName = self.partPrefix + p
|
||||||
|
view = component.queryMultiAdapter((self.adapted, self.request),
|
||||||
|
name=viewName)
|
||||||
|
if view is not None:
|
||||||
|
view.parent = self
|
||||||
|
result.append(view)
|
||||||
|
return result
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def adapted(self):
|
def adapted(self):
|
||||||
return adapted(self.context, self.languageInfo)
|
return adapted(self.context, self.languageInfo)
|
||||||
|
@ -296,6 +320,10 @@ class ConceptView(BaseView):
|
||||||
if r.order != pos:
|
if r.order != pos:
|
||||||
r.order = pos
|
r.order = pos
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def filterOptions(self):
|
||||||
|
return self.getOptions('filter.states')
|
||||||
|
|
||||||
def getChildren(self, topLevelOnly=True, sort=True, noDuplicates=True,
|
def getChildren(self, topLevelOnly=True, sort=True, noDuplicates=True,
|
||||||
useFilter=True, predicates=None):
|
useFilter=True, predicates=None):
|
||||||
form = self.request.form
|
form = self.request.form
|
||||||
|
@ -337,7 +365,7 @@ class ConceptView(BaseView):
|
||||||
options = IOptions(adapted(r.predicate), None)
|
options = IOptions(adapted(r.predicate), None)
|
||||||
if options is not None and options('hide_children'):
|
if options is not None and options('hide_children'):
|
||||||
continue
|
continue
|
||||||
if not fv.check(r.context):
|
if not fv.check(r.context, self.filterOptions):
|
||||||
continue
|
continue
|
||||||
yield r
|
yield r
|
||||||
|
|
||||||
|
@ -693,3 +721,22 @@ class ListChildren(ConceptView):
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return concept_macros.macros['list_children']
|
return concept_macros.macros['list_children']
|
||||||
|
|
||||||
|
|
||||||
|
class ListTypeInstances(ListChildren):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def targets(self):
|
||||||
|
targetPredicate = self.conceptManager['querytarget']
|
||||||
|
for c in self.context.getChildren([targetPredicate]):
|
||||||
|
# TODO: use type-specific view
|
||||||
|
yield ConceptView(c, self.request)
|
||||||
|
|
||||||
|
def children(self, topLevelOnly=True, sort=True, noDuplicates=True,
|
||||||
|
useFilter=True, predicates=None):
|
||||||
|
# TODO: use filter options of query for selection of children
|
||||||
|
for tv in self.targets:
|
||||||
|
tv.filterOptions = self.filterOptions
|
||||||
|
for c in tv.getChildren(topLevelOnly, sort,
|
||||||
|
noDuplicates, useFilter, [self.typePredicate]):
|
||||||
|
yield c
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<html i18n:domain="loops">
|
<html i18n:domain="loops">
|
||||||
|
|
||||||
|
|
||||||
|
<metal:data define-macro="layout">
|
||||||
|
<tal:part repeat="part item/getParts">
|
||||||
|
<metal:part use-macro="part/macro" />
|
||||||
|
</tal:part>
|
||||||
|
</metal:data>
|
||||||
|
|
||||||
|
|
||||||
<metal:data define-macro="conceptdata">
|
<metal:data define-macro="conceptdata">
|
||||||
<div tal:attributes="class string:content-$level;">
|
<div tal:attributes="class string:content-$level;">
|
||||||
<metal:block use-macro="view/concept_macros/concepttitle" />
|
<metal:block use-macro="view/concept_macros/concepttitle" />
|
||||||
|
@ -22,6 +29,20 @@
|
||||||
</metal:data>
|
</metal:data>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:selection define-macro="filter_input">
|
||||||
|
<div tal:define="criteria item/filterInput"
|
||||||
|
tal:condition="criteria">
|
||||||
|
<form method="get" name="filter" id="form-filter">
|
||||||
|
<span tal:repeat="crit criteria">
|
||||||
|
<metal:input use-macro="crit/macro" />
|
||||||
|
</span>
|
||||||
|
<input type="submit" name="show" value="Show"
|
||||||
|
tal:condition="nothing" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</metal:selection>
|
||||||
|
|
||||||
|
|
||||||
<metal:title define-macro="concepttitle">
|
<metal:title define-macro="concepttitle">
|
||||||
<metal:title define-macro="concepttitle_only">
|
<metal:title define-macro="concepttitle_only">
|
||||||
<tal:actions condition="view/showObjectActions">
|
<tal:actions condition="view/showObjectActions">
|
||||||
|
@ -41,8 +62,10 @@
|
||||||
string:$resourceBase/cybertools.icons/table.png" />
|
string:$resourceBase/cybertools.icons/table.png" />
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
<metal:block use-macro="view/concept_macros/filter_input" />
|
||||||
</metal:title>
|
</metal:title>
|
||||||
<p tal:define="description description|item/renderedDescription"
|
<p metal:define-macro="conceptdescription"
|
||||||
|
tal:define="description description|item/renderedDescription"
|
||||||
tal:condition="description">
|
tal:condition="description">
|
||||||
<i tal:content="structure description">Description</i></p>
|
<i tal:content="structure description">Description</i></p>
|
||||||
</metal:title>
|
</metal:title>
|
||||||
|
|
|
@ -545,6 +545,14 @@
|
||||||
factory="loops.browser.concept.ListChildren"
|
factory="loops.browser.concept.ListChildren"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="list_type_instances.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.browser.concept.ListTypeInstances"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
<!-- dialogs/forms (end-user views) -->
|
<!-- dialogs/forms (end-user views) -->
|
||||||
|
|
||||||
<page
|
<page
|
||||||
|
|
|
@ -291,7 +291,8 @@
|
||||||
tal:condition="stTrans"
|
tal:condition="stTrans"
|
||||||
tal:attributes="name string:state.$stDef">
|
tal:attributes="name string:state.$stDef">
|
||||||
<option i18n:translate="" value="-">No change</option>
|
<option i18n:translate="" value="-">No change</option>
|
||||||
<option tal:repeat="trans stTrans"
|
<option i18n:translate=""
|
||||||
|
tal:repeat="trans stTrans"
|
||||||
tal:attributes="value trans/name"
|
tal:attributes="value trans/name"
|
||||||
tal:content="trans/title">publish</option>
|
tal:content="trans/title">publish</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -309,7 +310,7 @@
|
||||||
<input value="Save" type="submit"
|
<input value="Save" type="submit"
|
||||||
i18n:attributes="value"
|
i18n:attributes="value"
|
||||||
tal:attributes="onClick python: view.closeAction(True) or
|
tal:attributes="onClick python: view.closeAction(True) or
|
||||||
'return true'">
|
'submit();; return false'">
|
||||||
<input type="button" value="Cancel" onClick="dlg.hide();"
|
<input type="button" value="Cancel" onClick="dlg.hide();"
|
||||||
i18n:attributes="value"
|
i18n:attributes="value"
|
||||||
tal:condition="view/isInnerHtml"
|
tal:condition="view/isInnerHtml"
|
||||||
|
@ -322,16 +323,15 @@
|
||||||
<!-- overridden field renderers -->
|
<!-- overridden field renderers -->
|
||||||
|
|
||||||
<metal:html define-macro="input_html">
|
<metal:html define-macro="input_html">
|
||||||
<p>HTML</p>
|
<div dojoType="dijit.Editor"
|
||||||
<div name="field" rows="3" style="width: 600px"
|
extraPlugins="['insertHorizontalRule', 'createLink', 'viewsource']"
|
||||||
dojoType="dijit.Editor"
|
|
||||||
extraPlugins="['insertHorizontalRule','createLink', 'viewsource']"
|
|
||||||
tal:define="width field/width|nothing;
|
tal:define="width field/width|nothing;
|
||||||
height field/height|python:3"
|
height field/height|python:10"
|
||||||
tal:attributes="name name; id name;
|
tal:attributes="name name; id name;
|
||||||
rows python: height or 3;
|
height python:
|
||||||
|
'%spx' % (height and str(height * 15) or '150');
|
||||||
style python:
|
style python:
|
||||||
'width: %s' % (width and str(width)+'px' or '600px');"
|
'width: %spx' % (width and str(width) or '600');"
|
||||||
tal:content="structure data/?name|string:">
|
tal:content="structure data/?name|string:">
|
||||||
</div>
|
</div>
|
||||||
</metal:html>
|
</metal:html>
|
||||||
|
|
|
@ -41,18 +41,12 @@ class Base(BaseConceptView):
|
||||||
templateName = 'lobo.standard'
|
templateName = 'lobo.standard'
|
||||||
macroName = None
|
macroName = None
|
||||||
|
|
||||||
@Lazy
|
#@Lazy
|
||||||
def macros(self):
|
# better implementation in BaseView:
|
||||||
return self.controller.getTemplateMacros(self.templateName, self.template)
|
# splits comma-separated list of values automatically
|
||||||
|
#def params(self):
|
||||||
@property
|
# ann = self.request.annotations.get('loops.view', {})
|
||||||
def macro(self):
|
# return parse_qs(ann.get('params') or '')
|
||||||
return self.macros[self.macroName]
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def params(self):
|
|
||||||
ann = self.request.annotations.get('loops.view', {})
|
|
||||||
return parse_qs(ann.get('params') or '')
|
|
||||||
|
|
||||||
|
|
||||||
class ConceptView(BaseConceptView):
|
class ConceptView(BaseConceptView):
|
||||||
|
@ -152,25 +146,8 @@ class ConceptView(BaseConceptView):
|
||||||
class Layout(Base, ConceptView):
|
class Layout(Base, ConceptView):
|
||||||
|
|
||||||
macroName = 'layout'
|
macroName = 'layout'
|
||||||
|
partPrefix = 'lobo_'
|
||||||
def getParts(self):
|
defaultParts = ('h1', 'g3',)
|
||||||
parts = (self.params.get('parts') or [''])[0].split(',') # obsolete
|
|
||||||
if not parts or not parts[0]:
|
|
||||||
parts = (self.options('parts') or
|
|
||||||
self.typeOptions('parts') or
|
|
||||||
['h1', 'g3'])
|
|
||||||
return self.getPartViews(parts)
|
|
||||||
|
|
||||||
def getPartViews(self, parts):
|
|
||||||
result = []
|
|
||||||
for p in parts:
|
|
||||||
viewName = 'lobo_' + p
|
|
||||||
view = component.queryMultiAdapter((self.adapted, self.request),
|
|
||||||
name=viewName)
|
|
||||||
if view is not None:
|
|
||||||
view.parent = self
|
|
||||||
result.append(view)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class BasePart(Base):
|
class BasePart(Base):
|
||||||
|
@ -192,7 +169,7 @@ class BasePart(Base):
|
||||||
return preds
|
return preds
|
||||||
|
|
||||||
def getChildren(self):
|
def getChildren(self):
|
||||||
subtypeNames = (self.params.get('subtypes') or [''])[0].split(',')
|
subtypeNames = (self.params.get('subtypes') or [])
|
||||||
subtypes = [self.conceptManager[st] for st in subtypeNames if st]
|
subtypes = [self.conceptManager[st] for st in subtypeNames if st]
|
||||||
result = []
|
result = []
|
||||||
childRels = self.context.getChildRelations(self.childPredicates)
|
childRels = self.context.getChildRelations(self.childPredicates)
|
||||||
|
|
|
@ -211,6 +211,22 @@ function closeDialog(save) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDataWidget(save) {
|
function closeDataWidget(save) {
|
||||||
|
form = dojo.byId('dialog_form');
|
||||||
|
dojo.query('.dijitEditor').forEach(function(item, index) {
|
||||||
|
console.log(item);
|
||||||
|
var name = item.id;
|
||||||
|
var widget = dijit.byId(name);
|
||||||
|
value = widget.getValue();
|
||||||
|
var ta = document.createElement('input');
|
||||||
|
ta.type = 'hidden';
|
||||||
|
ta.name = name;
|
||||||
|
ta.value = value;
|
||||||
|
form.appendChild(ta);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function xx_closeDataWidget(save) {
|
||||||
var widget = dijit.byId('data');
|
var widget = dijit.byId('data');
|
||||||
if (widget != undefined && save) {
|
if (widget != undefined && save) {
|
||||||
value = widget.getValue();
|
value = widget.getValue();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -51,17 +51,18 @@ from cybertools.typology.interfaces import IType, ITypeManager
|
||||||
from cybertools.util.jeep import Jeep
|
from cybertools.util.jeep import Jeep
|
||||||
from cybertools.xedit.browser import ExternalEditorView
|
from cybertools.xedit.browser import ExternalEditorView
|
||||||
from loops.browser.action import actions, DialogAction
|
from loops.browser.action import actions, DialogAction
|
||||||
|
from loops.browser.common import BaseView
|
||||||
|
from loops.browser.concept import ConceptView
|
||||||
from loops.common import adapted, AdapterBase, baseObject
|
from loops.common import adapted, AdapterBase, baseObject
|
||||||
from loops.i18n.browser import i18n_macros, LanguageInfo
|
from loops.i18n.browser import i18n_macros, LanguageInfo
|
||||||
from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode
|
from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode
|
||||||
from loops.interfaces import IViewConfiguratorSchema
|
from loops.interfaces import IViewConfiguratorSchema
|
||||||
from loops.resource import MediaAsset
|
|
||||||
from loops import util
|
|
||||||
from loops.util import _
|
|
||||||
from loops.browser.common import BaseView
|
|
||||||
from loops.browser.concept import ConceptView
|
|
||||||
from loops.organize.interfaces import IPresence
|
from loops.organize.interfaces import IPresence
|
||||||
from loops.organize.tracking import access
|
from loops.organize.tracking import access
|
||||||
|
from loops.resource import MediaAsset
|
||||||
|
from loops.security.common import canWriteObject
|
||||||
|
from loops import util
|
||||||
|
from loops.util import _
|
||||||
from loops.versioning.util import getVersion
|
from loops.versioning.util import getVersion
|
||||||
|
|
||||||
|
|
||||||
|
@ -150,13 +151,15 @@ class NodeView(BaseView):
|
||||||
priority=20)
|
priority=20)
|
||||||
cm.register('portlet_left', 'navigation', title='Navigation',
|
cm.register('portlet_left', 'navigation', title='Navigation',
|
||||||
subMacro=node_macros.macros['menu'])
|
subMacro=node_macros.macros['menu'])
|
||||||
if canWrite(self.context, 'title') or (
|
if canWriteObject(self.context) or (
|
||||||
# TODO: is this useful in any case?
|
# TODO: is this useful in any case?
|
||||||
self.virtualTargetObject is not None and
|
self.virtualTargetObject is not None and
|
||||||
canWrite(self.virtualTargetObject, 'title')):
|
canWriteObject(self.virtualTargetObject)):
|
||||||
# check if there are any available actions;
|
# check if there are any available actions;
|
||||||
# store list of actions in macro object (evaluate only once)
|
# store list of actions in macro object (evaluate only once)
|
||||||
actions = [act for act in self.getActions('portlet') if act.condition]
|
actions = [act for act in self.getAllowedActions('portlet',
|
||||||
|
target=self.virtualTarget)
|
||||||
|
if act.condition]
|
||||||
if actions:
|
if actions:
|
||||||
cm.register('portlet_right', 'actions', title=_(u'Actions'),
|
cm.register('portlet_right', 'actions', title=_(u'Actions'),
|
||||||
subMacro=node_macros.macros['actions'],
|
subMacro=node_macros.macros['actions'],
|
||||||
|
@ -537,7 +540,7 @@ class NodeView(BaseView):
|
||||||
return self.makeTargetUrl(self.url, util.getUidForObject(target),
|
return self.makeTargetUrl(self.url, util.getUidForObject(target),
|
||||||
target.title)
|
target.title)
|
||||||
|
|
||||||
def getActions(self, category='object', target=None):
|
def getActions(self, category='object', page=None, target=None):
|
||||||
actions = []
|
actions = []
|
||||||
#self.registerDojo()
|
#self.registerDojo()
|
||||||
self.registerDojoFormAll()
|
self.registerDojoFormAll()
|
||||||
|
@ -565,9 +568,11 @@ class NodeView(BaseView):
|
||||||
description='Open concept map editor in new window',
|
description='Open concept map editor in new window',
|
||||||
url=cmeUrl, target=target))
|
url=cmeUrl, target=target))
|
||||||
if self.checkAction('create_resource', 'portlet', target):
|
if self.checkAction('create_resource', 'portlet', target):
|
||||||
actions.append(DialogAction(self, title='Create Resource...',
|
actions.append(DialogAction(self, name='create_resource',
|
||||||
|
title='Create Resource...',
|
||||||
description='Create a new resource object.',
|
description='Create a new resource object.',
|
||||||
page=self, target=target))
|
page=self, target=target,
|
||||||
|
permission='zope.ManageContent'))
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
actions = dict(portlet=getPortletActions)
|
actions = dict(portlet=getPortletActions)
|
||||||
|
|
|
@ -293,7 +293,8 @@
|
||||||
<metal:actions define-macro="object_actions">
|
<metal:actions define-macro="object_actions">
|
||||||
<div id="object-actions" class="object-actions"
|
<div id="object-actions" class="object-actions"
|
||||||
tal:define="target nocall:target|nothing;">
|
tal:define="target nocall:target|nothing;">
|
||||||
<tal:actions repeat="action python:view.getActions('object', target=target)">
|
<tal:actions repeat="action python:
|
||||||
|
view.getAllowedActions('object', target=target)">
|
||||||
<metal:action use-macro="action/macro" />
|
<metal:action use-macro="action/macro" />
|
||||||
</tal:actions>
|
</tal:actions>
|
||||||
</div>
|
</div>
|
||||||
|
@ -322,7 +323,7 @@
|
||||||
<div><a href="logout.html?nextURL=login.html"
|
<div><a href="logout.html?nextURL=login.html"
|
||||||
tal:attributes="href string:logout.html?nextURL=${view/menu/url}"
|
tal:attributes="href string:logout.html?nextURL=${view/menu/url}"
|
||||||
i18n:translate="">Log out</a></div>
|
i18n:translate="">Log out</a></div>
|
||||||
<tal:actions repeat="action python:view.getActions('personal')">
|
<tal:actions repeat="action python:view.getAllowedActions('personal')">
|
||||||
<metal:action use-macro="action/macro" />
|
<metal:action use-macro="action/macro" />
|
||||||
</tal:actions>
|
</tal:actions>
|
||||||
</metal:actions>
|
</metal:actions>
|
||||||
|
|
|
@ -113,6 +113,9 @@ class AdapterBase(object):
|
||||||
self.context = context
|
self.context = context
|
||||||
self.__parent__ = context # to get the permission stuff right
|
self.__parent__ = context # to get the permission stuff right
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.context)
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
self.checkAttr(attr)
|
self.checkAttr(attr)
|
||||||
return getattr(self.context, '_' + attr, None)
|
return getattr(self.context, '_' + attr, None)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
View classes for glossary and glossary items.
|
View classes for glossary and glossary items.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +25,7 @@ import itertools
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.traversing.api import getName
|
||||||
|
|
||||||
from cybertools.browser.action import actions
|
from cybertools.browser.action import actions
|
||||||
from cybertools.browser.member import IMemberInfoProvider
|
from cybertools.browser.member import IMemberInfoProvider
|
||||||
|
@ -53,9 +52,36 @@ actions.register('createBlogPost', 'portlet', DialogAction,
|
||||||
fixedType=True,
|
fixedType=True,
|
||||||
innerForm='inner_concept_form.html',
|
innerForm='inner_concept_form.html',
|
||||||
prerequisites=['registerDojoDateWidget'], # +'registerDojoTextWidget'?
|
prerequisites=['registerDojoDateWidget'], # +'registerDojoTextWidget'?
|
||||||
|
permission='loops.AssignAsParent',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# blog lists
|
||||||
|
|
||||||
|
class BlogList(ConceptView):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return view_macros.macros['bloglist']
|
||||||
|
|
||||||
|
def children(self, topLevelOnly=True, sort=True, noDuplicates=True,
|
||||||
|
useFilter=True, predicates=None):
|
||||||
|
rels = self.getChildren(topLevelOnly, sort,
|
||||||
|
noDuplicates, useFilter, predicates)
|
||||||
|
return [rel for rel in rels if self.notEmpty(rel)]
|
||||||
|
|
||||||
|
def notEmpty(self, rel):
|
||||||
|
if self.request.form.get('filter.states') == 'all':
|
||||||
|
return True
|
||||||
|
# TODO: use type-specific view
|
||||||
|
view = ConceptView(rel.relation.second, self.request)
|
||||||
|
for c in view.children():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# blog view
|
||||||
|
|
||||||
def supplyCreator(self, data):
|
def supplyCreator(self, data):
|
||||||
creator = data.get('creator')
|
creator = data.get('creator')
|
||||||
data['creatorId'] = creator
|
data['creatorId'] = creator
|
||||||
|
@ -99,6 +125,8 @@ class BlogView(ConceptView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def blogOwnerId(self):
|
def blogOwnerId(self):
|
||||||
|
if getName(self.context.conceptType) != 'blog':
|
||||||
|
return ''
|
||||||
pType = self.loopsRoot.getConceptManager()['person']
|
pType = self.loopsRoot.getConceptManager()['person']
|
||||||
persons = [p for p in self.context.getParents() if p.conceptType == pType]
|
persons = [p for p in self.context.getParents() if p.conceptType == pType]
|
||||||
if len(persons) == 1:
|
if len(persons) == 1:
|
||||||
|
@ -127,6 +155,8 @@ class BlogView(ConceptView):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# blog post view
|
||||||
|
|
||||||
class BlogPostView(ConceptView):
|
class BlogPostView(ConceptView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -152,6 +182,7 @@ class BlogPostView(ConceptView):
|
||||||
description=_(u'Modify blog post.'),
|
description=_(u'Modify blog post.'),
|
||||||
viewName='edit_blogpost.html',
|
viewName='edit_blogpost.html',
|
||||||
dialogName='editBlogPost',
|
dialogName='editBlogPost',
|
||||||
|
permission='zope.ManageContent',
|
||||||
page=page, target=target))
|
page=page, target=target))
|
||||||
#self.registerDojoTextWidget()
|
#self.registerDojoTextWidget()
|
||||||
self.registerDojoDateWidget()
|
self.registerDojoDateWidget()
|
||||||
|
@ -170,6 +201,8 @@ class BlogPostView(ConceptView):
|
||||||
yield self.childViewFactory(r, self.request, contextIsSecond=True)
|
yield self.childViewFactory(r, self.request, contextIsSecond=True)
|
||||||
|
|
||||||
|
|
||||||
|
# forms and form controllers
|
||||||
|
|
||||||
class EditBlogPostForm(EditConceptForm):
|
class EditBlogPostForm(EditConceptForm):
|
||||||
|
|
||||||
title = _(u'Edit Blog Post')
|
title = _(u'Edit Blog Post')
|
||||||
|
|
|
@ -5,6 +5,19 @@
|
||||||
xmlns:browser="http://namespaces.zope.org/browser"
|
xmlns:browser="http://namespaces.zope.org/browser"
|
||||||
i18n_domain="zope">
|
i18n_domain="zope">
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
factory="loops.compound.blog.post.SimpleBlogPost"
|
||||||
|
provides="loops.compound.blog.interfaces.ISimpleBlogPost"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.compound.blog.post.SimpleBlogPost">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.compound.blog.interfaces.ISimpleBlogPost" />
|
||||||
|
<require permission="zope.View"
|
||||||
|
attributes="context" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.compound.blog.interfaces.ISimpleBlogPost" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.compound.blog.post.BlogPost"
|
factory="loops.compound.blog.post.BlogPost"
|
||||||
provides="loops.compound.blog.interfaces.IBlogPost"
|
provides="loops.compound.blog.interfaces.IBlogPost"
|
||||||
|
@ -24,6 +37,15 @@
|
||||||
|
|
||||||
<!-- views -->
|
<!-- views -->
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="bloglist.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.compound.blog.browser.BlogList"
|
||||||
|
permission="zope.View"
|
||||||
|
/>
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
name="blog.html"
|
name="blog.html"
|
||||||
for="loops.interfaces.IConcept
|
for="loops.interfaces.IConcept
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Blogs (weblogs) and blog posts.
|
Blogs (weblogs) and blog posts.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -27,9 +25,32 @@ from zope.interface import Interface, Attribute
|
||||||
from zope import interface, component, schema
|
from zope import interface, component, schema
|
||||||
|
|
||||||
from loops.compound.interfaces import ICompound
|
from loops.compound.interfaces import ICompound
|
||||||
|
from loops.interfaces import HtmlText
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
class ISimpleBlogPost(ICompound):
|
||||||
|
""" An item on a blog, sort of a diary item, minimal version.
|
||||||
|
"""
|
||||||
|
|
||||||
|
date = schema.Datetime(
|
||||||
|
title=_(u'Date/Time'),
|
||||||
|
description=_(u'The date and time the information '
|
||||||
|
'was posted.'),
|
||||||
|
required=True,)
|
||||||
|
date.default_method = datetime.now
|
||||||
|
creator = schema.ASCIILine(
|
||||||
|
title=_(u'Creator'),
|
||||||
|
description=_(u'The principal id of the user that created '
|
||||||
|
'the blog post.'),
|
||||||
|
readonly=True,
|
||||||
|
required=False,)
|
||||||
|
text = HtmlText(
|
||||||
|
title=_(u'Text'),
|
||||||
|
description=_(u'The text of your blog entry'),
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
class IBlogPost(ICompound):
|
class IBlogPost(ICompound):
|
||||||
""" An item on a blog, sort of a diary item.
|
""" An item on a blog, sort of a diary item.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Blogs and blog posts.
|
Blogs and blog posts.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
@ -32,14 +30,31 @@ from zope.traversing.api import getName
|
||||||
|
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.compound.base import Compound
|
from loops.compound.base import Compound
|
||||||
from loops.compound.blog.interfaces import IBlogPost
|
from loops.compound.blog.interfaces import ISimpleBlogPost, IBlogPost
|
||||||
from loops.resource import Resource
|
from loops.resource import Resource
|
||||||
from loops.security.common import restrictView
|
from loops.security.common import restrictView
|
||||||
from loops.setup import addAndConfigureObject
|
from loops.setup import addAndConfigureObject
|
||||||
from loops.type import TypeInterfaceSourceList
|
from loops.type import TypeInterfaceSourceList
|
||||||
|
|
||||||
|
|
||||||
TypeInterfaceSourceList.typeInterfaces += (IBlogPost,)
|
TypeInterfaceSourceList.typeInterfaces += (ISimpleBlogPost, IBlogPost,)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleBlogPost(Compound):
|
||||||
|
|
||||||
|
implements(ISimpleBlogPost)
|
||||||
|
|
||||||
|
textContentType = 'text/html'
|
||||||
|
|
||||||
|
_adapterAttributes = Compound._adapterAttributes + ('creator',)
|
||||||
|
_contextAttributes = list(ISimpleBlogPost)
|
||||||
|
_noexportAttributes = _adapterAttributes
|
||||||
|
_textIndexAttributes = ('text',)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def creator(self):
|
||||||
|
cr = IZopeDublinCore(self.context).creators
|
||||||
|
return cr and cr[0] or None
|
||||||
|
|
||||||
|
|
||||||
class BlogPost(Compound):
|
class BlogPost(Compound):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Specialized schema factories
|
Specialized schema factories
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.component import adapts
|
from zope.component import adapts
|
||||||
|
@ -34,6 +32,6 @@ class BlogPostSchemaFactory(SchemaFactory):
|
||||||
|
|
||||||
def __call__(self, interface, **kw):
|
def __call__(self, interface, **kw):
|
||||||
schema = super(BlogPostSchemaFactory, self).__call__(interface, **kw)
|
schema = super(BlogPostSchemaFactory, self).__call__(interface, **kw)
|
||||||
schema.fields.text.height = 10
|
schema.fields.text.height = 15
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Security settings for blogs and blog posts.
|
Security settings for blogs and blog posts.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
@ -30,10 +28,10 @@ from zope.traversing.api import getName
|
||||||
from loops.compound.blog.interfaces import IBlogPost
|
from loops.compound.blog.interfaces import IBlogPost
|
||||||
from loops.security.common import allowEditingForOwner, assignOwner, restrictView
|
from loops.security.common import allowEditingForOwner, assignOwner, restrictView
|
||||||
from loops.security.common import getCurrentPrincipal
|
from loops.security.common import getCurrentPrincipal
|
||||||
from loops.security.setter import BaseSecuritySetter
|
from loops.security.setter import LoopsObjectSecuritySetter
|
||||||
|
|
||||||
|
|
||||||
class BlogPostSecuritySetter(BaseSecuritySetter):
|
class BlogPostSecuritySetter(LoopsObjectSecuritySetter):
|
||||||
|
|
||||||
adapts(IBlogPost)
|
adapts(IBlogPost)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<!-- ZPT macros for loops.compound.blog views
|
<html i18n:domain="loops">
|
||||||
$Id$ -->
|
|
||||||
|
|
||||||
|
<metal:block define-macro="bloglist">
|
||||||
|
<div tal:define="show_headline nothing">
|
||||||
|
<metal:fields use-macro="item/template/macros/concepttitle" /><br />
|
||||||
|
<metal:fields use-macro="item/template/macros/conceptchildren" />
|
||||||
|
</div>
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
<metal:block define-macro="blog">
|
<metal:block define-macro="blog">
|
||||||
<div>
|
<div>
|
||||||
|
@ -7,9 +15,12 @@
|
||||||
<div tal:repeat="related item/blogPosts"
|
<div tal:repeat="related item/blogPosts"
|
||||||
class="blog">
|
class="blog">
|
||||||
<tal:child define="data related/data">
|
<tal:child define="data related/data">
|
||||||
|
<tal:actions condition="view/showObjectActions"
|
||||||
|
define="target nocall:related">
|
||||||
|
<div metal:use-macro="views/node_macros/object_actions" />
|
||||||
|
</tal:actions>
|
||||||
<h1 class="headline">
|
<h1 class="headline">
|
||||||
<a href="#"
|
<a tal:content="related/title"
|
||||||
tal:content="related/title"
|
|
||||||
tal:attributes="href python: view.getUrlForTarget(related);">Post</a>
|
tal:attributes="href python: view.getUrlForTarget(related);">Post</a>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="info"
|
<div class="info"
|
||||||
|
@ -51,7 +62,7 @@
|
||||||
<a tal:omit-tag="not:url"
|
<a tal:omit-tag="not:url"
|
||||||
tal:content="data/creator"
|
tal:content="data/creator"
|
||||||
tal:attributes="href url">Will Smith</a>
|
tal:attributes="href url">Will Smith</a>
|
||||||
<span tal:condition="item/adapted/private">
|
<span tal:condition="item/adapted/private|nothing">
|
||||||
(<span i18n:translate="">Private</span>)
|
(<span i18n:translate="">Private</span>)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +74,7 @@
|
||||||
<div class="text"
|
<div class="text"
|
||||||
tal:content="structure item/render">Here comes the text...</div>
|
tal:content="structure item/render">Here comes the text...</div>
|
||||||
<div class="comment"
|
<div class="comment"
|
||||||
tal:define="comment data/privateComment"
|
tal:define="comment data/privateComment|nothing"
|
||||||
tal:condition="comment">
|
tal:condition="comment">
|
||||||
<h4 i18n:translate="" class="headline">Private Comment</h4>
|
<h4 i18n:translate="" class="headline">Private Comment</h4>
|
||||||
<div tal:content="structure python:
|
<div tal:content="structure python:
|
||||||
|
@ -72,3 +83,6 @@
|
||||||
<metal:resources use-macro="view/concept_macros/conceptresources" />
|
<metal:resources use-macro="view/concept_macros/conceptresources" />
|
||||||
<metal:block use-macro="view/comment_macros/comments" />
|
<metal:block use-macro="view/comment_macros/comments" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
|
@ -150,7 +150,7 @@ class PageLayout(Base, standard.Layout):
|
||||||
|
|
||||||
def getParts(self):
|
def getParts(self):
|
||||||
parts = ['headline', 'keyquestions', 'quote', 'maintext',
|
parts = ['headline', 'keyquestions', 'quote', 'maintext',
|
||||||
'story', 'usecase']
|
'story', 'tip', 'usecase']
|
||||||
return self.getPartViews(parts)
|
return self.getPartViews(parts)
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,6 +191,11 @@ class Story(PagePart, standard.BasePart):
|
||||||
partName = 'story'
|
partName = 'story'
|
||||||
|
|
||||||
|
|
||||||
|
class Tip(PagePart, standard.BasePart):
|
||||||
|
|
||||||
|
partName = 'tip'
|
||||||
|
|
||||||
|
|
||||||
class UseCase(PagePart, standard.BasePart):
|
class UseCase(PagePart, standard.BasePart):
|
||||||
|
|
||||||
partName = 'usecase'
|
partName = 'usecase'
|
||||||
|
|
|
@ -75,6 +75,14 @@
|
||||||
factory="loops.compound.book.browser.Story"
|
factory="loops.compound.book.browser.Story"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="lobo_tip"
|
||||||
|
for="loops.compound.book.interfaces.IPage
|
||||||
|
loops.browser.skin.Lobo"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.compound.book.browser.Tip"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
name="lobo_usecase"
|
name="lobo_usecase"
|
||||||
for="loops.compound.book.interfaces.IPage
|
for="loops.compound.book.interfaces.IPage
|
||||||
|
|
|
@ -24,6 +24,7 @@ concept(u'textelement', u'Textabschnitt', u'documenttype')
|
||||||
concept(u'textelement2', u'Textabschnitt separat', u'documenttype')
|
concept(u'textelement2', u'Textabschnitt separat', u'documenttype')
|
||||||
concept(u'quote', u'Zitat', u'documenttype')
|
concept(u'quote', u'Zitat', u'documenttype')
|
||||||
concept(u'story', u'Geschichte', u'documenttype')
|
concept(u'story', u'Geschichte', u'documenttype')
|
||||||
|
concept(u'tip', u'Tipp', u'documenttype')
|
||||||
concept(u'usecase', u'Fallbeispiel', u'documenttype')
|
concept(u'usecase', u'Fallbeispiel', u'documenttype')
|
||||||
|
|
||||||
# book structure
|
# book structure
|
||||||
|
|
|
@ -48,6 +48,7 @@ from loops.base import ParentInfo
|
||||||
from loops.common import adapted, AdapterBase
|
from loops.common import adapted, AdapterBase
|
||||||
from loops.i18n.common import I18NValue
|
from loops.i18n.common import I18NValue
|
||||||
from loops.interfaces import IConcept, IConceptRelation, IConceptView
|
from loops.interfaces import IConcept, IConceptRelation, IConceptView
|
||||||
|
from loops.interfaces import IResource
|
||||||
from loops.interfaces import IConceptManager, IConceptManagerContained
|
from loops.interfaces import IConceptManager, IConceptManagerContained
|
||||||
from loops.interfaces import ILoopsContained
|
from loops.interfaces import ILoopsContained
|
||||||
from loops.interfaces import IIndexAttributes
|
from loops.interfaces import IIndexAttributes
|
||||||
|
@ -199,7 +200,8 @@ class Concept(Contained, Persistent):
|
||||||
if sort == 'default':
|
if sort == 'default':
|
||||||
sort = lambda x: (x.order, (x.second.title and x.second.title.lower()))
|
sort = lambda x: (x.order, (x.second.title and x.second.title.lower()))
|
||||||
rels = (r for r in getRelations(self, child, relationships=relationships)
|
rels = (r for r in getRelations(self, child, relationships=relationships)
|
||||||
if canListObject(r.second, noSecurityCheck))
|
if canListObject(r.second, noSecurityCheck) and
|
||||||
|
IConcept.providedBy(r.second))
|
||||||
return sorted(rels, key=sort)
|
return sorted(rels, key=sort)
|
||||||
|
|
||||||
def getChildren(self, predicates=None, sort='default', noSecurityCheck=False):
|
def getChildren(self, predicates=None, sort='default', noSecurityCheck=False):
|
||||||
|
@ -294,7 +296,8 @@ class Concept(Contained, Persistent):
|
||||||
if sort == 'default':
|
if sort == 'default':
|
||||||
sort = lambda x: (x.order, x.second.title.lower())
|
sort = lambda x: (x.order, x.second.title.lower())
|
||||||
rels = (r for r in getRelations(self, resource, relationships=relationships)
|
rels = (r for r in getRelations(self, resource, relationships=relationships)
|
||||||
if canListObject(r.second, noSecurityCheck))
|
if canListObject(r.second, noSecurityCheck) and
|
||||||
|
IResource.providedBy(r.second))
|
||||||
return sorted(rels, key=sort)
|
return sorted(rels, key=sort)
|
||||||
|
|
||||||
def getResources(self, predicates=None, sort='default', noSecurityCheck=False):
|
def getResources(self, predicates=None, sort='default', noSecurityCheck=False):
|
||||||
|
|
|
@ -27,7 +27,7 @@ configuration):
|
||||||
>>> concepts, resources, views = t.setup()
|
>>> concepts, resources, views = t.setup()
|
||||||
|
|
||||||
>>> len(concepts) + len(resources)
|
>>> len(concepts) + len(resources)
|
||||||
37
|
36
|
||||||
|
|
||||||
>>> loopsRoot = site['loops']
|
>>> loopsRoot = site['loops']
|
||||||
|
|
||||||
|
@ -47,11 +47,11 @@ Type- and text-based queries
|
||||||
>>> from loops.expert import query
|
>>> from loops.expert import query
|
||||||
>>> qu = query.Title('ty*')
|
>>> qu = query.Title('ty*')
|
||||||
>>> list(qu.apply())
|
>>> list(qu.apply())
|
||||||
[0, 2, 68]
|
[0, 2, 65]
|
||||||
|
|
||||||
>>> qu = query.Type('loops:*')
|
>>> qu = query.Type('loops:*')
|
||||||
>>> len(list(qu.apply()))
|
>>> len(list(qu.apply()))
|
||||||
37
|
36
|
||||||
|
|
||||||
>>> qu = query.Type('loops:concept:predicate')
|
>>> qu = query.Type('loops:concept:predicate')
|
||||||
>>> len(list(qu.apply()))
|
>>> len(list(qu.apply()))
|
||||||
|
|
|
@ -75,4 +75,20 @@
|
||||||
class="loops.expert.browser.report.ResultsView"
|
class="loops.expert.browser.report.ResultsView"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="concept_report.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.expert.browser.report.ReportConceptView"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="concept_results.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.expert.browser.report.ResultsConceptView"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
|
|
|
@ -165,10 +165,20 @@ class ResultsConceptView(ConceptView):
|
||||||
def hasReportPredicate(self):
|
def hasReportPredicate(self):
|
||||||
return self.conceptManager['hasreport']
|
return self.conceptManager['hasreport']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def reportName(self):
|
||||||
|
return (self.getOptions('report_name') or [None])[0]
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def reportType(self):
|
||||||
|
return (self.getOptions('report_type') or [None])[0]
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def report(self):
|
def report(self):
|
||||||
if self.reportName:
|
if self.reportName:
|
||||||
return adapted(self.conceptManager[self.reportName])
|
return adapted(self.conceptManager[self.reportName])
|
||||||
|
reports = self.context.getParents([self.hasReportPredicate])
|
||||||
|
if not reports:
|
||||||
type = self.context.conceptType
|
type = self.context.conceptType
|
||||||
reports = type.getParents([self.hasReportPredicate])
|
reports = type.getParents([self.hasReportPredicate])
|
||||||
return adapted(reports[0])
|
return adapted(reports[0])
|
||||||
|
|
|
@ -368,7 +368,8 @@
|
||||||
i18n:translate=""></td>
|
i18n:translate=""></td>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<tal:states repeat="state def/states">
|
<tal:states repeat="state def/states">
|
||||||
<tal:state define="name string:state.$deftype.${def/name};
|
<tal:state define="xx_name string:state.$deftype.${def/name};
|
||||||
|
name string:state.${def/name};
|
||||||
value state/name">
|
value state/name">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
tal:attributes="name string:$name:list;
|
tal:attributes="name string:$name:list;
|
||||||
|
|
|
@ -196,8 +196,9 @@ class Search(ConceptView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def statesDefinitions(self):
|
def statesDefinitions(self):
|
||||||
return [component.getUtility(IStatesDefinition, name=n)
|
stdnames = (self.globalOptions('organize.stateful.resource', []) +
|
||||||
for n in self.globalOptions('organize.stateful.resource', ())]
|
self.globalOptions('organize.stateful.special', []))
|
||||||
|
return [component.getUtility(IStatesDefinition, name=n) for n in stdnames]
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def selectedStates(self):
|
def selectedStates(self):
|
||||||
|
@ -259,6 +260,8 @@ class Search(ConceptView):
|
||||||
for std, states in self.selectedStates.items():
|
for std, states in self.selectedStates.items():
|
||||||
if std.startswith('state.resource.'):
|
if std.startswith('state.resource.'):
|
||||||
std = std[len('state.resource.'):]
|
std = std[len('state.resource.'):]
|
||||||
|
elif std.startswith('state.'):
|
||||||
|
std = std[len('state.'):]
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
stf = component.queryAdapter(obj, IStateful, name=std)
|
stf = component.queryAdapter(obj, IStateful, name=std)
|
||||||
|
|
|
@ -38,6 +38,10 @@
|
||||||
set_schema="loops.expert.report.IReportInstance" />
|
set_schema="loops.expert.report.IReportInstance" />
|
||||||
</class>
|
</class>
|
||||||
|
|
||||||
|
<adapter factory="loops.expert.standard.TypeInstances"
|
||||||
|
name="type_instances"
|
||||||
|
provides="loops.expert.report.IReportInstance" />
|
||||||
|
|
||||||
<utility
|
<utility
|
||||||
provides="zope.schema.interfaces.IVocabularyFactory"
|
provides="zope.schema.interfaces.IVocabularyFactory"
|
||||||
component="loops.expert.report.ReportTypeSourceList"
|
component="loops.expert.report.ReportTypeSourceList"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -60,6 +60,11 @@ class TextField(Field):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
class HtmlTextField(Field):
|
||||||
|
|
||||||
|
format = 'text/html'
|
||||||
|
|
||||||
|
|
||||||
class DecimalField(Field):
|
class DecimalField(Field):
|
||||||
|
|
||||||
format = 'decimal'
|
format = 'decimal'
|
||||||
|
|
|
@ -101,9 +101,11 @@ class ReportInstance(BaseReport):
|
||||||
|
|
||||||
def getResults(self, dynaParams=None):
|
def getResults(self, dynaParams=None):
|
||||||
crit = self.queryCriteria
|
crit = self.queryCriteria
|
||||||
if crit is None:
|
|
||||||
return []
|
|
||||||
limits = self.limits
|
limits = self.limits
|
||||||
|
if crit is None:
|
||||||
|
parts = Jeep()
|
||||||
|
#return ResultSet(self, [])
|
||||||
|
else:
|
||||||
if dynaParams is not None:
|
if dynaParams is not None:
|
||||||
for k, v in dynaParams.items():
|
for k, v in dynaParams.items():
|
||||||
if k == 'limits':
|
if k == 'limits':
|
||||||
|
|
|
@ -66,13 +66,13 @@ zcml in real life:
|
||||||
|
|
||||||
>>> t = searchView.typesForSearch()
|
>>> t = searchView.typesForSearch()
|
||||||
>>> len(t)
|
>>> len(t)
|
||||||
16
|
15
|
||||||
>>> t.getTermByToken('loops:resource:*').title
|
>>> t.getTermByToken('loops:resource:*').title
|
||||||
'Any Resource'
|
'Any Resource'
|
||||||
|
|
||||||
>>> t = searchView.conceptTypesForSearch()
|
>>> t = searchView.conceptTypesForSearch()
|
||||||
>>> len(t)
|
>>> len(t)
|
||||||
13
|
12
|
||||||
>>> t.getTermByToken('loops:concept:*').title
|
>>> t.getTermByToken('loops:concept:*').title
|
||||||
'Any Concept'
|
'Any Concept'
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ a controller attribute for the search view.
|
||||||
|
|
||||||
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
|
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
|
||||||
'submitReplacing("1.results", "1.search.form",
|
'submitReplacing("1.results", "1.search.form",
|
||||||
"http://127.0.0.1/loops/views/page/.target99/@@searchresults.html");...'
|
"http://127.0.0.1/loops/views/page/.target96/@@searchresults.html");...'
|
||||||
|
|
||||||
Basic (text/title) search
|
Basic (text/title) search
|
||||||
-------------------------
|
-------------------------
|
||||||
|
@ -177,7 +177,7 @@ of the concepts' titles:
|
||||||
>>> request = TestRequest(form=form)
|
>>> request = TestRequest(form=form)
|
||||||
>>> view = Search(page, request)
|
>>> view = Search(page, request)
|
||||||
>>> view.listConcepts()
|
>>> view.listConcepts()
|
||||||
u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '104'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '106'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '108'}]}"
|
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'}]}"
|
||||||
|
|
||||||
Preset Concept Types on Search Forms
|
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')
|
>>> searchView.conceptsForType('loops:concept:customer')
|
||||||
[{'token': 'none', 'title': u'not selected'},
|
[{'token': 'none', 'title': u'not selected'},
|
||||||
{'token': '77', 'title': u'Customer 1'},
|
{'token': '74', 'title': u'Customer 1'},
|
||||||
{'token': '79', 'title': u'Customer 2'},
|
{'token': '76', 'title': u'Customer 2'},
|
||||||
{'token': '81', 'title': u'Customer 3'}]
|
{'token': '78', 'title': u'Customer 3'}]
|
||||||
|
|
||||||
Let's use this new search option for querying:
|
Let's use this new search option for querying:
|
||||||
|
|
||||||
>>> form = {'search.4.text_selected': u'77'}
|
>>> form = {'search.4.text_selected': u'74'}
|
||||||
>>> resultsView = SearchResults(page, TestRequest(form=form))
|
>>> resultsView = SearchResults(page, TestRequest(form=form))
|
||||||
>>> results = list(resultsView.results)
|
>>> results = list(resultsView.results)
|
||||||
>>> results[0].title
|
>>> results[0].title
|
||||||
|
|
54
expert/standard.py
Normal file
54
expert/standard.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
loops standard reports.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
|
||||||
|
from cybertools.util.jeep import Jeep
|
||||||
|
from loops.browser.concept import ConceptView
|
||||||
|
from loops.expert.field import Field, TargetField, DateField, StateField, \
|
||||||
|
TextField, HtmlTextField, UrlField
|
||||||
|
from loops.expert.report import ReportInstance
|
||||||
|
|
||||||
|
|
||||||
|
title = UrlField('title', u'Title',
|
||||||
|
description=u'A short descriptive text.',
|
||||||
|
executionSteps=['output'])
|
||||||
|
|
||||||
|
|
||||||
|
class TypeInstances(ReportInstance):
|
||||||
|
|
||||||
|
fields = Jeep((title,))
|
||||||
|
defaultOutputFields = fields
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def targets(self):
|
||||||
|
targetPredicate = self.view.conceptManager['querytarget']
|
||||||
|
return self.view.context.getChildren([targetPredicate])
|
||||||
|
|
||||||
|
def selectObjects(self, parts):
|
||||||
|
result = []
|
||||||
|
for t in self.targets:
|
||||||
|
for c in t.getChildren([self.view.typePredicate]):
|
||||||
|
result.append(c)
|
||||||
|
print '***', self.targets, result
|
||||||
|
return result
|
||||||
|
|
6
external/README.txt
vendored
6
external/README.txt
vendored
|
@ -17,7 +17,7 @@ Let's set up a loops site with basic and example concepts and resources.
|
||||||
>>> concepts, resources, views = t.setup()
|
>>> concepts, resources, views = t.setup()
|
||||||
>>> loopsRoot = site['loops']
|
>>> loopsRoot = site['loops']
|
||||||
>>> len(concepts), len(resources), len(views)
|
>>> len(concepts), len(resources), len(views)
|
||||||
(34, 3, 1)
|
(33, 3, 1)
|
||||||
|
|
||||||
|
|
||||||
Importing loops Objects
|
Importing loops Objects
|
||||||
|
@ -44,7 +44,7 @@ Creating the corresponding objects
|
||||||
>>> loader = Loader(loopsRoot)
|
>>> loader = Loader(loopsRoot)
|
||||||
>>> loader.load(elements)
|
>>> loader.load(elements)
|
||||||
>>> len(concepts), len(resources), len(views)
|
>>> len(concepts), len(resources), len(views)
|
||||||
(35, 3, 1)
|
(34, 3, 1)
|
||||||
|
|
||||||
>>> from loops.common import adapted
|
>>> from loops.common import adapted
|
||||||
>>> adMyquery = adapted(concepts['myquery'])
|
>>> adMyquery = adapted(concepts['myquery'])
|
||||||
|
@ -131,7 +131,7 @@ Extracting elements
|
||||||
>>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export'))
|
>>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export'))
|
||||||
>>> elements = list(extractor.extract())
|
>>> elements = list(extractor.extract())
|
||||||
>>> len(elements)
|
>>> len(elements)
|
||||||
68
|
66
|
||||||
|
|
||||||
Writing object information to the external storage
|
Writing object information to the external storage
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
|
@ -197,6 +197,8 @@ class DirectoryCollectionProvider(object):
|
||||||
extFileType = extFileTypes.get(contentType.split('/')[0] + '/*')
|
extFileType = extFileTypes.get(contentType.split('/')[0] + '/*')
|
||||||
if extFileType is None:
|
if extFileType is None:
|
||||||
extFileType = extFileTypes['*/*']
|
extFileType = extFileTypes['*/*']
|
||||||
|
if extFileType is None:
|
||||||
|
extFileType = extFileTypes['image/*']
|
||||||
if extFileType is None:
|
if extFileType is None:
|
||||||
getLogger('loops.integrator.collection.DirectoryCollectionProvider'
|
getLogger('loops.integrator.collection.DirectoryCollectionProvider'
|
||||||
).warn('No external file type found for %r, '
|
).warn('No external file type found for %r, '
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -30,6 +30,7 @@ from zope.app.file.interfaces import IImage as IBaseAsset
|
||||||
from zope.component.interfaces import IObjectEvent
|
from zope.component.interfaces import IObjectEvent
|
||||||
from zope.size.interfaces import ISized
|
from zope.size.interfaces import ISized
|
||||||
|
|
||||||
|
from cybertools.composer.schema.interfaces import FieldType
|
||||||
from cybertools.relation.interfaces import IDyadicRelation
|
from cybertools.relation.interfaces import IDyadicRelation
|
||||||
from cybertools.tracking.interfaces import ITrackingStorage
|
from cybertools.tracking.interfaces import ITrackingStorage
|
||||||
from cybertools.util.format import toStr, toUnicode
|
from cybertools.util.format import toStr, toUnicode
|
||||||
|
@ -37,6 +38,11 @@ from loops import util
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
class HtmlText(schema.Text):
|
||||||
|
|
||||||
|
__typeInfo__ = ('html',)
|
||||||
|
|
||||||
|
|
||||||
# common interfaces
|
# common interfaces
|
||||||
|
|
||||||
class ILoopsObject(Interface):
|
class ILoopsObject(Interface):
|
||||||
|
|
|
@ -170,28 +170,7 @@ For testing, we first have to provide the needed utilities and settings
|
||||||
Competence and Certification Management
|
Competence and Certification Management
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
>>> from cybertools.stateful.interfaces import IStatesDefinition
|
>>> tCompetence = concepts['competence']
|
||||||
>>> from loops.knowledge.qualification import qualificationStates
|
|
||||||
>>> from loops.knowledge.interfaces import IQualificationRecords
|
|
||||||
>>> from loops.knowledge.qualification import QualificationRecords
|
|
||||||
>>> component.provideUtility(qualificationStates,
|
|
||||||
... provides=IStatesDefinition,
|
|
||||||
... name='knowledge.qualification')
|
|
||||||
>>> component.provideAdapter(QualificationRecords,
|
|
||||||
... provides=IQualificationRecords)
|
|
||||||
|
|
||||||
>>> qurecs = loopsRoot.getRecordManager()['qualification']
|
|
||||||
|
|
||||||
We first create a training that provides knowledge in Python specials.
|
|
||||||
|
|
||||||
>>> trainingPySpecC = concepts['trpyspec'] = Concept(
|
|
||||||
... u'Python Specials Training')
|
|
||||||
>>> trainingPySpecC.assignParent(pySpecialsC)
|
|
||||||
|
|
||||||
Then we record the need for John to acquire this knowledge.
|
|
||||||
|
|
||||||
>>> from loops.knowledge.browser import CreateQualificationRecordForm
|
|
||||||
>>> from loops.knowledge.browser import CreateQualificationRecord
|
|
||||||
|
|
||||||
|
|
||||||
Glossaries
|
Glossaries
|
||||||
|
@ -205,6 +184,15 @@ Glossary items are topic-like concepts that may be edited by end users.
|
||||||
>>> from loops.knowledge.glossary.browser import EditGlossaryItem
|
>>> from loops.knowledge.glossary.browser import EditGlossaryItem
|
||||||
|
|
||||||
|
|
||||||
|
Survey
|
||||||
|
======
|
||||||
|
|
||||||
|
>>> from loops.knowledge.tests import importSurvey
|
||||||
|
>>> importSurvey(loopsRoot)
|
||||||
|
|
||||||
|
>>> from loops.knowledge.survey.browser import SurveyView
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -30,10 +30,7 @@ from cybertools.typology.interfaces import IType
|
||||||
from loops.browser.action import DialogAction
|
from loops.browser.action import DialogAction
|
||||||
from loops.browser.common import BaseView
|
from loops.browser.common import BaseView
|
||||||
from loops.browser.concept import ConceptView
|
from loops.browser.concept import ConceptView
|
||||||
from loops.expert.browser.report import ResultsConceptView
|
|
||||||
from loops.knowledge.interfaces import IPerson, ITask
|
from loops.knowledge.interfaces import IPerson, ITask
|
||||||
from loops.knowledge.qualification import QualificationRecord
|
|
||||||
from loops.organize.work.browser import CreateWorkItemForm, CreateWorkItem
|
|
||||||
from loops.organize.party import getPersonForUser
|
from loops.organize.party import getPersonForUser
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
@ -50,6 +47,7 @@ actions.register('createTopic', 'portlet', DialogAction,
|
||||||
typeToken='.loops/concepts/topic',
|
typeToken='.loops/concepts/topic',
|
||||||
fixedType=True,
|
fixedType=True,
|
||||||
innerForm='inner_concept_form.html',
|
innerForm='inner_concept_form.html',
|
||||||
|
permission='loops.AssignAsParent',
|
||||||
)
|
)
|
||||||
|
|
||||||
actions.register('editTopic', 'portlet', DialogAction,
|
actions.register('editTopic', 'portlet', DialogAction,
|
||||||
|
@ -57,6 +55,7 @@ actions.register('editTopic', 'portlet', DialogAction,
|
||||||
description=_(u'Modify topic.'),
|
description=_(u'Modify topic.'),
|
||||||
viewName='edit_concept.html',
|
viewName='edit_concept.html',
|
||||||
dialogName='editTopic',
|
dialogName='editTopic',
|
||||||
|
permission='zope.ManageContent',
|
||||||
)
|
)
|
||||||
|
|
||||||
actions.register('createQualification', 'portlet', DialogAction,
|
actions.register('createQualification', 'portlet', DialogAction,
|
||||||
|
@ -66,6 +65,7 @@ actions.register('createQualification', 'portlet', DialogAction,
|
||||||
dialogName='createQualification',
|
dialogName='createQualification',
|
||||||
prerequisites=['registerDojoDateWidget', 'registerDojoNumberWidget',
|
prerequisites=['registerDojoDateWidget', 'registerDojoNumberWidget',
|
||||||
'registerDojoTextarea'],
|
'registerDojoTextarea'],
|
||||||
|
permission='loops.AssignAsParent',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,25 +111,3 @@ class Candidates(ConceptView):
|
||||||
return self.template.macros['requirement_candidates']
|
return self.template.macros['requirement_candidates']
|
||||||
|
|
||||||
|
|
||||||
# qualification stuff
|
|
||||||
|
|
||||||
class PersonQualificationView(ResultsConceptView):
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CreateQualificationRecordForm(CreateWorkItemForm):
|
|
||||||
|
|
||||||
macros = knowledge_macros
|
|
||||||
recordManagerName = 'qualification'
|
|
||||||
trackFactory = QualificationRecord
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def macro(self):
|
|
||||||
return self.macros['create_qualification']
|
|
||||||
|
|
||||||
|
|
||||||
class CreateQualificationRecord(CreateWorkItem):
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
<!-- $Id$ -->
|
|
||||||
|
|
||||||
<configure
|
<configure
|
||||||
xmlns:zope="http://namespaces.zope.org/zope"
|
xmlns:zope="http://namespaces.zope.org/zope"
|
||||||
xmlns:browser="http://namespaces.zope.org/browser"
|
xmlns:browser="http://namespaces.zope.org/browser"
|
||||||
|
@ -66,22 +64,6 @@
|
||||||
interface="cybertools.knowledge.interfaces.IKnowledgeProvider" />
|
interface="cybertools.knowledge.interfaces.IKnowledgeProvider" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
<!-- records -->
|
|
||||||
|
|
||||||
<zope:class class="loops.knowledge.qualification.QualificationRecord">
|
|
||||||
<require permission="zope.View"
|
|
||||||
interface="loops.knowledge.interfaces.IQualificationRecord" />
|
|
||||||
<require permission="zope.ManageContent"
|
|
||||||
set_schema="loops.knowledge.interfaces.IQualificationRecord" />
|
|
||||||
</zope:class>
|
|
||||||
|
|
||||||
<zope:adapter factory="loops.knowledge.qualification.QualificationRecords"
|
|
||||||
provides="loops.knowledge.interfaces.IQualificationRecords" />
|
|
||||||
|
|
||||||
<zope:utility factory="loops.knowledge.qualification.qualificationStates"
|
|
||||||
provides="cybertools.stateful.interfaces.IStatesDefinition"
|
|
||||||
name="knowledge.qualification" />
|
|
||||||
|
|
||||||
<!-- views -->
|
<!-- views -->
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
|
@ -100,19 +82,6 @@
|
||||||
factory="loops.knowledge.browser.Candidates"
|
factory="loops.knowledge.browser.Candidates"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
<browser:page
|
|
||||||
name="create_qualification.html"
|
|
||||||
for="loops.interfaces.INode"
|
|
||||||
class="loops.knowledge.browser.CreateQualificationRecordForm"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
name="create_qualification"
|
|
||||||
for="loops.browser.node.NodeView
|
|
||||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
|
||||||
factory="loops.knowledge.browser.CreateQualificationRecord"
|
|
||||||
permission="zope.View" />
|
|
||||||
|
|
||||||
<!-- other adapters -->
|
<!-- other adapters -->
|
||||||
|
|
||||||
<zope:adapter factory="loops.knowledge.schema.PersonSchemaFactory" />
|
<zope:adapter factory="loops.knowledge.schema.PersonSchemaFactory" />
|
||||||
|
@ -120,5 +89,7 @@
|
||||||
<!-- sub-packages -->
|
<!-- sub-packages -->
|
||||||
|
|
||||||
<include package=".glossary" />
|
<include package=".glossary" />
|
||||||
|
<include package=".qualification" />
|
||||||
|
<include package=".survey" />
|
||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
type(u'competence', u'Kompetenz', viewName=u'',
|
type(u'competence', u'Kompetenz', viewName=u'',
|
||||||
typeInterface=u'', options=u'action.portlet:create_subtype,edit_concept')
|
typeInterface=u'loops.knowledge.qualification.interfaces.ICompetence',
|
||||||
|
options=u'action.portlet:create_subtype,edit_concept')
|
||||||
type(u'person', u'Person', viewName=u'',
|
type(u'person', u'Person', viewName=u'',
|
||||||
typeInterface=u'loops.knowledge.interfaces.IPerson',
|
typeInterface=u'loops.knowledge.interfaces.IPerson',
|
||||||
options=u'action.portlet:createQualification,editPerson')
|
options=u'action.portlet:createQualification,editPerson')
|
||||||
|
@ -9,9 +10,9 @@ type(u'task', u'Aufgabe', viewName=u'',
|
||||||
type(u'topic', u'Thema', viewName=u'',
|
type(u'topic', u'Thema', viewName=u'',
|
||||||
typeInterface=u'loops.knowledge.interfaces.ITopic',
|
typeInterface=u'loops.knowledge.interfaces.ITopic',
|
||||||
options=u'action.portlet:createTask,createTopic,editTopic')
|
options=u'action.portlet:createTask,createTopic,editTopic')
|
||||||
type(u'training', u'Schulung', viewName=u'',
|
#type(u'training', u'Schulung', viewName=u'',
|
||||||
typeInterface=u'loops.organize.interfaces.ITask',
|
# typeInterface=u'loops.organize.interfaces.ITask',
|
||||||
options=u'action.portlet:edit_concept')
|
# options=u'action.portlet:edit_concept')
|
||||||
|
|
||||||
concept(u'general', u'Allgemein', u'domain')
|
concept(u'general', u'Allgemein', u'domain')
|
||||||
concept(u'system', u'System', u'domain')
|
concept(u'system', u'System', u'domain')
|
||||||
|
@ -34,11 +35,12 @@ child(u'general', u'provides', u'standard')
|
||||||
child(u'general', u'requires', u'standard')
|
child(u'general', u'requires', u'standard')
|
||||||
child(u'general', u'task', u'standard')
|
child(u'general', u'task', u'standard')
|
||||||
child(u'general', u'topic', u'standard')
|
child(u'general', u'topic', u'standard')
|
||||||
child(u'general', u'training', u'standard')
|
#child(u'general', u'training', u'standard')
|
||||||
|
|
||||||
child(u'system', u'issubtype', u'standard')
|
child(u'system', u'issubtype', u'standard')
|
||||||
|
|
||||||
child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
|
child(u'competence', u'competence', u'issubtype')
|
||||||
|
#child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
|
||||||
|
|
||||||
# records
|
# records
|
||||||
records(u'qualification', u'loops.knowledge.qualification.QualificationRecord')
|
#records(u'qualification', u'loops.knowledge.qualification.base.QualificationRecord')
|
|
@ -1,5 +1,6 @@
|
||||||
type(u'competence', u'Kompetenz', viewName=u'',
|
type(u'competence', u'Kompetenz', viewName=u'',
|
||||||
typeInterface=u'', options=u'action.portlet:create_subtype,edit_concept')
|
typeInterface=u'loops.knowledge.qualification.interfaces.ICompetence',
|
||||||
|
options=u'action.portlet:create_subtype,edit_concept')
|
||||||
# type(u'person', u'Person', viewName=u'',
|
# type(u'person', u'Person', viewName=u'',
|
||||||
# typeInterface=u'loops.knowledge.interfaces.IPerson',
|
# typeInterface=u'loops.knowledge.interfaces.IPerson',
|
||||||
# options=u'action.portlet:editPerson')
|
# options=u'action.portlet:editPerson')
|
||||||
|
@ -9,9 +10,9 @@ type(u'competence', u'Kompetenz', viewName=u'',
|
||||||
# type(u'topic', u'Thema', viewName=u'',
|
# type(u'topic', u'Thema', viewName=u'',
|
||||||
# typeInterface=u'loops.knowledge.interfaces.ITopic',
|
# typeInterface=u'loops.knowledge.interfaces.ITopic',
|
||||||
# options=u'action.portlet:createTask,createTopic,editTopic')
|
# options=u'action.portlet:createTask,createTopic,editTopic')
|
||||||
type(u'training', u'Schulung', viewName=u'',
|
#type(u'training', u'Schulung', viewName=u'',
|
||||||
typeInterface=u'loops.organize.interfaces.ITask',
|
# typeInterface=u'loops.organize.interfaces.ITask',
|
||||||
options=u'action.portlet:edit_concept')
|
# options=u'action.portlet:edit_concept')
|
||||||
|
|
||||||
concept(u'general', u'Allgemein', u'domain')
|
concept(u'general', u'Allgemein', u'domain')
|
||||||
concept(u'system', u'System', u'domain')
|
concept(u'system', u'System', u'domain')
|
||||||
|
@ -34,11 +35,12 @@ child(u'general', u'provides', u'standard')
|
||||||
child(u'general', u'requires', u'standard')
|
child(u'general', u'requires', u'standard')
|
||||||
#child(u'general', u'task', u'standard')
|
#child(u'general', u'task', u'standard')
|
||||||
#child(u'general', u'topic', u'standard')
|
#child(u'general', u'topic', u'standard')
|
||||||
child(u'general', u'training', u'standard')
|
#child(u'general', u'training', u'standard')
|
||||||
|
|
||||||
child(u'system', u'issubtype', u'standard')
|
child(u'system', u'issubtype', u'standard')
|
||||||
|
|
||||||
child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
|
child(u'competence', u'competence', u'issubtype')
|
||||||
|
#child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
|
||||||
|
|
||||||
# records
|
# records
|
||||||
records(u'qualification', u'loops.knowledge.qualification.QualificationRecord')
|
#records(u'qualification', u'loops.knowledge.qualification.base.QualificationRecord')
|
25
knowledge/data/survey_de.dmp
Normal file
25
knowledge/data/survey_de.dmp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# survey types
|
||||||
|
type(u'questionnaire', u'Fragebogen', viewName=u'survey.html',
|
||||||
|
typeInterface=u'loops.knowledge.survey.interfaces.IQuestionnaire',
|
||||||
|
options=u'action.portlet:create_subtype,edit_concept')
|
||||||
|
type(u'questiongroup', u'Fragengruppe', viewName=u'',
|
||||||
|
typeInterface=u'loops.knowledge.survey.interfaces.IQuestionGroup',
|
||||||
|
options=u'action.portlet:create_subtype,edit_concept\nchildren_append\nshow_navigation')
|
||||||
|
type(u'question', u'Frage', viewName=u'',
|
||||||
|
typeInterface=u'loops.knowledge.survey.interfaces.IQuestion',
|
||||||
|
options=u'action.portlet:edit_concept\nshow_navigation')
|
||||||
|
#options=u'action.portlet:create_subtype,edit_concept')
|
||||||
|
type(u'feedbackitem', u'Feedback-Element', viewName=u'',
|
||||||
|
typeInterface=u'loops.knowledge.survey.interfaces.IFeedbackItem',
|
||||||
|
options=u'action.portlet:edit_concept\nshow_navigation')
|
||||||
|
|
||||||
|
# subtypes
|
||||||
|
#child(u'questionnaire', u'questionnaire', u'issubtype')
|
||||||
|
#child(u'questionnaire', u'question', u'issubtype')
|
||||||
|
child(u'questionnaire', u'questiongroup', u'issubtype')
|
||||||
|
child(u'questiongroup', u'question', u'issubtype')
|
||||||
|
child(u'questiongroup', u'feedbackitem', u'issubtype')
|
||||||
|
#child(u'question', u'feedbackitem', u'issubtype')
|
||||||
|
|
||||||
|
# records
|
||||||
|
records(u'survey_responses', u'loops.knowledge.survey.response.Response')
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,18 +22,14 @@ Interfaces for knowledge management and elearning with loops.
|
||||||
|
|
||||||
from zope.interface import Interface, Attribute
|
from zope.interface import Interface, Attribute
|
||||||
from zope import interface, component, schema
|
from zope import interface, component, schema
|
||||||
from zope.i18nmessageid import MessageFactory
|
|
||||||
from zope.security.proxy import removeSecurityProxy
|
|
||||||
|
|
||||||
from cybertools.knowledge.interfaces import IKnowing, IRequirementProfile
|
from cybertools.knowledge.interfaces import IKnowing, IRequirementProfile
|
||||||
from cybertools.knowledge.interfaces import IKnowledgeElement
|
from cybertools.knowledge.interfaces import IKnowledgeElement
|
||||||
from cybertools.organize.interfaces import IWorkItem, IWorkItems
|
|
||||||
from loops.interfaces import IConceptSchema, ILoopsAdapter
|
from loops.interfaces import IConceptSchema, ILoopsAdapter
|
||||||
from loops.organize.interfaces import IPerson as IBasePerson
|
from loops.organize.interfaces import IPerson as IBasePerson
|
||||||
from loops.organize.interfaces import ITask as IBaseTask
|
from loops.organize.interfaces import ITask as IBaseTask
|
||||||
from loops.schema.base import Relation, RelationSet
|
from loops.schema.base import Relation, RelationSet
|
||||||
|
from loops.util import _
|
||||||
_ = MessageFactory('loops')
|
|
||||||
|
|
||||||
|
|
||||||
class IPerson(IBasePerson, IKnowing):
|
class IPerson(IBasePerson, IKnowing):
|
||||||
|
@ -62,13 +58,3 @@ class ITopic(IConceptSchema, IKnowledgeElement, ILoopsAdapter):
|
||||||
""" Just a topic, some general classification concept.
|
""" Just a topic, some general classification concept.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class IQualificationRecord(IWorkItem):
|
|
||||||
""" Records needs for qualification (acqusition of competence)
|
|
||||||
and corresponding participations in training events etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IQualificationRecords(IWorkItems):
|
|
||||||
""" Container for qualification records.
|
|
||||||
"""
|
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2012 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
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Controlling qualification activities of persons.
|
|
||||||
|
|
||||||
Central part of CCM competence and certification management framework.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.component import adapts
|
|
||||||
from zope.interface import implementer, implements
|
|
||||||
|
|
||||||
from cybertools.stateful.base import Stateful
|
|
||||||
from cybertools.stateful.definition import StatesDefinition
|
|
||||||
from cybertools.stateful.definition import State, Transition
|
|
||||||
from cybertools.stateful.interfaces import IStatesDefinition
|
|
||||||
from cybertools.tracking.interfaces import ITrackingStorage
|
|
||||||
from loops.knowledge.interfaces import IQualificationRecord, \
|
|
||||||
IQualificationRecords
|
|
||||||
from loops.organize.work.base import WorkItem, WorkItems
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(IStatesDefinition)
|
|
||||||
def qualificationStates():
|
|
||||||
return StatesDefinition('qualification',
|
|
||||||
State('new', 'new', ('assign',),
|
|
||||||
color='grey'),
|
|
||||||
State('open', 'open',
|
|
||||||
('register',
|
|
||||||
#'pass', 'fail',
|
|
||||||
'cancel', 'modify'),
|
|
||||||
color='red'),
|
|
||||||
State('registered', 'registered',
|
|
||||||
('register', 'pass', 'fail', 'unregister', 'cancel', 'modify'),
|
|
||||||
color='yellow'),
|
|
||||||
State('passed', 'passed',
|
|
||||||
('cancel', 'close', 'modify', 'open', 'expire'),
|
|
||||||
color='green'),
|
|
||||||
State('failed', 'failed',
|
|
||||||
('register', 'cancel', 'modify', 'open'),
|
|
||||||
color='green'),
|
|
||||||
State('expired', 'expired',
|
|
||||||
('register', 'cancel', 'modify', 'open'),
|
|
||||||
color='red'),
|
|
||||||
State('cancelled', 'cancelled', ('modify', 'open'),
|
|
||||||
color='grey'),
|
|
||||||
State('closed', 'closed', ('modify', 'open'),
|
|
||||||
color='lightblue'),
|
|
||||||
# not directly reachable states:
|
|
||||||
State('open_x', 'open', ('modify',), color='red'),
|
|
||||||
State('registered_x', 'registered', ('modify',), color='yellow'),
|
|
||||||
# transitions:
|
|
||||||
Transition('assign', 'assign', 'open'),
|
|
||||||
Transition('register', 'register', 'registered'),
|
|
||||||
Transition('pass', 'pass', 'passed'),
|
|
||||||
Transition('fail', 'fail', 'failed'),
|
|
||||||
Transition('unregister', 'unregister', 'open'),
|
|
||||||
Transition('cancel', 'cancel', 'cancelled'),
|
|
||||||
Transition('modify', 'modify', 'open'),
|
|
||||||
Transition('close', 'close', 'closed'),
|
|
||||||
Transition('open', 'open', 'open'),
|
|
||||||
#initialState='open')
|
|
||||||
initialState='new') # TODO: handle assignment to competence
|
|
||||||
|
|
||||||
|
|
||||||
class QualificationRecord(WorkItem):
|
|
||||||
|
|
||||||
implements(IQualificationRecord)
|
|
||||||
|
|
||||||
typeName = 'QualificationRecord'
|
|
||||||
typeInterface = IQualificationRecord
|
|
||||||
statesDefinition = 'knowledge.qualification'
|
|
||||||
|
|
||||||
def doAction(self, action, userName, **kw):
|
|
||||||
new = self.createNew(action, userName, **kw)
|
|
||||||
new.userName = self.userName
|
|
||||||
new.doTransition(action)
|
|
||||||
new.reindex()
|
|
||||||
return new
|
|
||||||
|
|
||||||
|
|
||||||
class QualificationRecords(WorkItems):
|
|
||||||
""" A tracking storage adapter managing qualification records.
|
|
||||||
"""
|
|
||||||
|
|
||||||
implements(IQualificationRecords)
|
|
||||||
adapts(ITrackingStorage)
|
|
||||||
|
|
1
knowledge/qualification/__init__.py
Normal file
1
knowledge/qualification/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
'''package loops.knowledge.qualification'''
|
42
knowledge/qualification/base.py
Normal file
42
knowledge/qualification/base.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Controlling qualification activities of persons.
|
||||||
|
|
||||||
|
Central part of CCM competence and certification management framework.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.component import adapts
|
||||||
|
from zope.interface import implementer, implements
|
||||||
|
|
||||||
|
from loops.common import AdapterBase
|
||||||
|
from loops.knowledge.qualification.interfaces import ICompetence
|
||||||
|
from loops.type import TypeInterfaceSourceList
|
||||||
|
|
||||||
|
|
||||||
|
TypeInterfaceSourceList.typeInterfaces += (ICompetence,)
|
||||||
|
|
||||||
|
|
||||||
|
class Competence(AdapterBase):
|
||||||
|
|
||||||
|
implements(ICompetence)
|
||||||
|
|
||||||
|
_contextAttributes = list(ICompetence)
|
||||||
|
|
||||||
|
|
36
knowledge/qualification/browser.py
Normal file
36
knowledge/qualification/browser.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Definition of view classes and other browser related stuff for the
|
||||||
|
loops.knowledge package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope import interface, component
|
||||||
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
|
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):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
11
knowledge/qualification/configure.zcml
Normal file
11
knowledge/qualification/configure.zcml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<configure
|
||||||
|
xmlns:zope="http://namespaces.zope.org/zope"
|
||||||
|
xmlns:browser="http://namespaces.zope.org/browser"
|
||||||
|
i18n_domain="loops">
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
factory="loops.knowledge.qualification.base.Competence" />
|
||||||
|
|
||||||
|
<!-- views -->
|
||||||
|
|
||||||
|
</configure>
|
44
knowledge/qualification/interfaces.py
Normal file
44
knowledge/qualification/interfaces.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interfaces for knowledge management and elearning with loops.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.interface import Interface, Attribute
|
||||||
|
from zope import interface, component, schema
|
||||||
|
|
||||||
|
from loops.interfaces import IConceptSchema
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
class ICompetence(IConceptSchema):
|
||||||
|
""" The competence of a person.
|
||||||
|
|
||||||
|
Maybe assigned to the person via a 'knows' relation or
|
||||||
|
work items of type 'checkup'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
validityPeriod = schema.Int(
|
||||||
|
title=_(u'Validity Period (Months)'),
|
||||||
|
description=_(u'Number of months the competence remains valid. '
|
||||||
|
u'Zero means unlimited validity.'),
|
||||||
|
default=0,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
1
knowledge/survey/__init__.py
Normal file
1
knowledge/survey/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
'''package loops.knowledge.survey'''
|
124
knowledge/survey/base.py
Normal file
124
knowledge/survey/base.py
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Surveys used in knowledge management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.component import adapts
|
||||||
|
from zope.interface import implementer, implements
|
||||||
|
|
||||||
|
from cybertools.knowledge.survey.questionnaire import Questionnaire, \
|
||||||
|
QuestionGroup, Question, FeedbackItem
|
||||||
|
from loops.common import adapted, AdapterBase
|
||||||
|
from loops.knowledge.survey.interfaces import IQuestionnaire, \
|
||||||
|
IQuestionGroup, IQuestion, IFeedbackItem
|
||||||
|
from loops.type import TypeInterfaceSourceList
|
||||||
|
|
||||||
|
|
||||||
|
TypeInterfaceSourceList.typeInterfaces += (IQuestionnaire,
|
||||||
|
IQuestionGroup, IQuestion, IFeedbackItem)
|
||||||
|
|
||||||
|
|
||||||
|
class Questionnaire(AdapterBase, Questionnaire):
|
||||||
|
|
||||||
|
implements(IQuestionnaire)
|
||||||
|
|
||||||
|
_contextAttributes = list(IQuestionnaire)
|
||||||
|
_adapterAttributes = AdapterBase._adapterAttributes + (
|
||||||
|
'questionGroups', 'questions', 'responses',)
|
||||||
|
_noexportAttributes = _adapterAttributes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def questionGroups(self):
|
||||||
|
return [adapted(c) for c in self.context.getChildren()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def questions(self):
|
||||||
|
for qug in self.questionGroups:
|
||||||
|
for qu in qug.questions:
|
||||||
|
#qu.questionnaire = self
|
||||||
|
yield qu
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionGroup(AdapterBase, QuestionGroup):
|
||||||
|
|
||||||
|
implements(IQuestionGroup)
|
||||||
|
|
||||||
|
_contextAttributes = list(IQuestionGroup)
|
||||||
|
_adapterAttributes = AdapterBase._adapterAttributes + (
|
||||||
|
'questionnaire', 'questions', 'feedbackItems')
|
||||||
|
_noexportAttributes = _adapterAttributes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def questionnaire(self):
|
||||||
|
for p in self.context.getParents():
|
||||||
|
ap = adapted(p)
|
||||||
|
if IQuestionnaire.providedBy(ap):
|
||||||
|
return ap
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subobjects(self):
|
||||||
|
return [adapted(c) for c in self.context.getChildren()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def questions(self):
|
||||||
|
return [obj for obj in self.subobjects if IQuestion.providedBy(obj)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def feedbackItems(self):
|
||||||
|
return [obj for obj in self.subobjects if IFeedbackItem.providedBy(obj)]
|
||||||
|
|
||||||
|
|
||||||
|
class Question(AdapterBase, Question):
|
||||||
|
|
||||||
|
implements(IQuestion)
|
||||||
|
|
||||||
|
_contextAttributes = list(IQuestion)
|
||||||
|
_adapterAttributes = AdapterBase._adapterAttributes + (
|
||||||
|
'text', 'questionnaire', 'answerRange', 'feedbackItems',)
|
||||||
|
_noexportAttributes = _adapterAttributes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
return self.context.description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def questionGroup(self):
|
||||||
|
for p in self.context.getParents():
|
||||||
|
ap = adapted(p)
|
||||||
|
if IQuestionGroup.providedBy(ap):
|
||||||
|
return ap
|
||||||
|
|
||||||
|
@property
|
||||||
|
def questionnaire(self):
|
||||||
|
return self.questionGroup.questionnaire
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackItem(AdapterBase, FeedbackItem):
|
||||||
|
|
||||||
|
implements(IFeedbackItem)
|
||||||
|
|
||||||
|
_contextAttributes = list(IFeedbackItem)
|
||||||
|
_adapterAttributes = AdapterBase._adapterAttributes + (
|
||||||
|
'text',)
|
||||||
|
_noexportAttributes = _adapterAttributes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
return self.context.description
|
174
knowledge/survey/browser.py
Normal file
174
knowledge/survey/browser.py
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Definition of view classes and other browser related stuff for
|
||||||
|
surveys and self-assessments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
from cStringIO import StringIO
|
||||||
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.i18n import translate
|
||||||
|
|
||||||
|
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.knowledge.survey.response import Responses
|
||||||
|
from loops.organize.party import getPersonForUser
|
||||||
|
from loops.util import getObjectForUid
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
template = ViewPageTemplateFile('view_macros.pt')
|
||||||
|
|
||||||
|
class SurveyView(ConceptView):
|
||||||
|
|
||||||
|
tabview = 'index.html'
|
||||||
|
data = None
|
||||||
|
errors = None
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return template.macros['survey']
|
||||||
|
|
||||||
|
def results(self):
|
||||||
|
result = []
|
||||||
|
response = None
|
||||||
|
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_'):
|
||||||
|
uid = key[len('question_'):]
|
||||||
|
question = adapted(self.getObjectForUid(uid))
|
||||||
|
if value != 'none':
|
||||||
|
value = int(value)
|
||||||
|
self.data[uid] = value
|
||||||
|
response.values[question] = value
|
||||||
|
Responses(self.context).save(self.data)
|
||||||
|
self.errors = self.check(response)
|
||||||
|
if self.errors:
|
||||||
|
return []
|
||||||
|
if response is not None:
|
||||||
|
result = response.getGroupedResult()
|
||||||
|
return [dict(category=r[0].title, text=r[1].text,
|
||||||
|
score=int(round(r[2] * 100)))
|
||||||
|
for r in result]
|
||||||
|
|
||||||
|
def check(self, response):
|
||||||
|
errors = []
|
||||||
|
values = response.values
|
||||||
|
for qu in self.adapted.questions:
|
||||||
|
if qu.required and qu not in values:
|
||||||
|
errors.append('Please answer the obligatory questions.')
|
||||||
|
break
|
||||||
|
qugroups = {}
|
||||||
|
for qugroup in self.adapted.questionGroups:
|
||||||
|
qugroups[qugroup] = 0
|
||||||
|
for qu in values:
|
||||||
|
qugroups[qu.questionGroup] += 1
|
||||||
|
for qugroup, count in qugroups.items():
|
||||||
|
minAnswers = qugroup.minAnswers
|
||||||
|
if minAnswers in (u'', None):
|
||||||
|
minAnswers = len(qugroup.questions)
|
||||||
|
if count < minAnswers:
|
||||||
|
errors.append('Please answer the minimum number of questions.')
|
||||||
|
break
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def getInfoText(self, qugroup):
|
||||||
|
lang = self.languageInfo.language
|
||||||
|
text = qugroup.description
|
||||||
|
info = None
|
||||||
|
if qugroup.minAnswers in (u'', None):
|
||||||
|
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)),
|
||||||
|
target_language=lang)
|
||||||
|
if info:
|
||||||
|
text = u'%s<br />(%s)' % (text, info)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def getValues(self, question):
|
||||||
|
setting = None
|
||||||
|
if self.data is None:
|
||||||
|
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))]
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyCsvExport(NodeView):
|
||||||
|
|
||||||
|
encoding = 'ISO8859-15'
|
||||||
|
|
||||||
|
def encode(self, text):
|
||||||
|
text.encode(self.encoding)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def questions(self):
|
||||||
|
result = []
|
||||||
|
for idx1, qug in enumerate(adapted(self.virtualTargetObject).questionGroups):
|
||||||
|
for idx2, qu in enumerate(qug.questions):
|
||||||
|
result.append((idx1, idx2, qug, qu))
|
||||||
|
return result
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def columns(self):
|
||||||
|
infoCols = ['Name', 'Timestamp']
|
||||||
|
dataCols = ['%02i-%02i' % (item[0], item[1]) for item in self.questions]
|
||||||
|
return infoCols + dataCols
|
||||||
|
|
||||||
|
def getRows(self):
|
||||||
|
for tr in Responses(self.virtualTargetObject).getAllTracks():
|
||||||
|
p = adapted(getObjectForUid(tr.userName))
|
||||||
|
name = p and p.title or u'???'
|
||||||
|
ts = formatTimeStamp(tr.timeStamp)
|
||||||
|
cells = [tr.data.get(qu.uid, -1)
|
||||||
|
for (idx1, idx2, qug, qu) in self.questions]
|
||||||
|
yield [name, ts] + cells
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
f = StringIO()
|
||||||
|
writer = csv.writer(f, delimiter=',')
|
||||||
|
writer.writerow(self.columns)
|
||||||
|
for row in self.getRows():
|
||||||
|
writer.writerow(row)
|
||||||
|
text = f.getvalue()
|
||||||
|
self.setDownloadHeader(text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def setDownloadHeader(self, text):
|
||||||
|
response = self.request.response
|
||||||
|
filename = 'survey_data.csv'
|
||||||
|
response.setHeader('Content-Disposition',
|
||||||
|
'attachment; filename=%s' % filename)
|
||||||
|
response.setHeader('Cache-Control', '')
|
||||||
|
response.setHeader('Pragma', '')
|
||||||
|
response.setHeader('Content-Length', len(text))
|
||||||
|
response.setHeader('Content-Type', 'text/csv')
|
||||||
|
|
76
knowledge/survey/configure.zcml
Normal file
76
knowledge/survey/configure.zcml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<configure
|
||||||
|
xmlns:zope="http://namespaces.zope.org/zope"
|
||||||
|
xmlns:browser="http://namespaces.zope.org/browser"
|
||||||
|
i18n_domain="loops">
|
||||||
|
|
||||||
|
<!-- concept classes -->
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
factory="loops.knowledge.survey.base.Questionnaire"
|
||||||
|
provides="loops.knowledge.survey.interfaces.IQuestionnaire"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.Questionnaire">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IQuestionnaire" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IQuestionnaire" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
factory="loops.knowledge.survey.base.QuestionGroup"
|
||||||
|
provides="loops.knowledge.survey.interfaces.IQuestionGroup"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.QuestionGroup">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IQuestionGroup" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IQuestionGroup" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
factory="loops.knowledge.survey.base.Question"
|
||||||
|
provides="loops.knowledge.survey.interfaces.IQuestion"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.Question">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IQuestion" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IQuestion" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
factory="loops.knowledge.survey.base.FeedbackItem"
|
||||||
|
provides="loops.knowledge.survey.interfaces.IFeedbackItem"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.knowledge.survey.base.FeedbackItem">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.knowledge.survey.interfaces.IFeedbackItem" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.knowledge.survey.interfaces.IFeedbackItem" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<!-- track -->
|
||||||
|
|
||||||
|
<zope:class class="loops.knowledge.survey.response.Response">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="cybertools.tracking.interfaces.ITrack" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="cybertools.tracking.interfaces.ITrack" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<!-- views -->
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="survey.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.knowledge.survey.browser.SurveyView"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page name="survey_data.csv"
|
||||||
|
for="loops.interfaces.IView"
|
||||||
|
class="loops.knowledge.survey.browser.SurveyCsvExport"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
|
</configure>
|
93
knowledge/survey/interfaces.py
Normal file
93
knowledge/survey/interfaces.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interfaces for surveys used in knowledge management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.interface import Interface, Attribute
|
||||||
|
from zope import interface, component, schema
|
||||||
|
|
||||||
|
from cybertools.knowledge.survey import interfaces
|
||||||
|
from loops.interfaces import IConceptSchema, ILoopsAdapter
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire):
|
||||||
|
""" A collection of questions for setting up a survey.
|
||||||
|
"""
|
||||||
|
|
||||||
|
defaultAnswerRange = schema.Int(
|
||||||
|
title=_(u'Answer Range'),
|
||||||
|
description=_(u'Number of items (answer options) to select from.'),
|
||||||
|
default=4,
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
feedbackFooter = schema.Text(
|
||||||
|
title=_(u'Feedback Footer'),
|
||||||
|
description=_(u'Text that will appear at the end of the feedback page.'),
|
||||||
|
default=u'',
|
||||||
|
missing_value=u'',
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class IQuestionGroup(IConceptSchema, interfaces.IQuestionGroup):
|
||||||
|
""" A group of questions within a questionnaire.
|
||||||
|
"""
|
||||||
|
|
||||||
|
minAnswers = schema.Int(
|
||||||
|
title=_(u'Minimum Number of Answers'),
|
||||||
|
description=_(u'Minumum number of questions that have to be answered. '
|
||||||
|
'Empty means all questions have to be answered.'),
|
||||||
|
default=None,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class IQuestion(IConceptSchema, interfaces.IQuestion):
|
||||||
|
""" A single question within a questionnaire.
|
||||||
|
"""
|
||||||
|
|
||||||
|
required = schema.Bool(
|
||||||
|
title=_(u'Required'),
|
||||||
|
description=_(u'Question must be answered.'),
|
||||||
|
default=False,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
revertAnswerOptions = schema.Bool(
|
||||||
|
title=_(u'Negative'),
|
||||||
|
description=_(u'Value inversion: High selection means low value.'),
|
||||||
|
default=False,
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class IFeedbackItem(IConceptSchema, interfaces.IFeedbackItem):
|
||||||
|
""" Some text (e.g. a recommendation) or some other kind of information
|
||||||
|
that may be deduced from the res)ponses to a questionnaire.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IResponse(interfaces.IResponse):
|
||||||
|
""" A set of response values given to the questions of a questionnaire
|
||||||
|
by a single person or party.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IResponses(Interface):
|
||||||
|
""" A container or manager of survey responses.
|
||||||
|
"""
|
||||||
|
|
63
knowledge/survey/response.py
Normal file
63
knowledge/survey/response.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Handling survey responses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.component import adapts
|
||||||
|
from zope.interface import implements
|
||||||
|
|
||||||
|
from cybertools.tracking.btree import Track
|
||||||
|
from cybertools.tracking.interfaces import ITrackingStorage
|
||||||
|
from loops.knowledge.survey.interfaces import IResponse, IResponses
|
||||||
|
from loops.organize.tracking.base import BaseRecordManager
|
||||||
|
|
||||||
|
|
||||||
|
class Responses(BaseRecordManager):
|
||||||
|
|
||||||
|
implements(IResponses)
|
||||||
|
|
||||||
|
storageName = 'survey_responses'
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
def save(self, data):
|
||||||
|
if not self.personId:
|
||||||
|
return
|
||||||
|
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)
|
||||||
|
if tracks:
|
||||||
|
return tracks[0].data
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def getAllTracks(self):
|
||||||
|
return self.storage.query(taskId=self.uid)
|
||||||
|
|
||||||
|
|
||||||
|
class Response(Track):
|
||||||
|
|
||||||
|
implements(IResponse)
|
||||||
|
|
||||||
|
typeName = 'Response'
|
||||||
|
|
93
knowledge/survey/view_macros.pt
Normal file
93
knowledge/survey/view_macros.pt
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<!-- ZPT macros for loops.knowledge.survey views -->
|
||||||
|
<html i18n:domain="loops">
|
||||||
|
|
||||||
|
|
||||||
|
<metal:block define-macro="survey"
|
||||||
|
tal:define="feedback item/results;
|
||||||
|
errors item/errors">
|
||||||
|
<metal:title use-macro="item/conceptMacros/concepttitle_only" />
|
||||||
|
<tal:description condition="not:feedback">
|
||||||
|
<metal:title use-macro="item/conceptMacros/conceptdescription" />
|
||||||
|
</tal:description>
|
||||||
|
<div tal:condition="feedback">
|
||||||
|
<h3 i18n:translate="">Feedback</h3>
|
||||||
|
<table class="listing">
|
||||||
|
<tr>
|
||||||
|
<th i18n:translate="">Category</th>
|
||||||
|
<th i18n:translate="">Response</th>
|
||||||
|
<th i18n:translate="">%</th>
|
||||||
|
</tr>
|
||||||
|
<tr tal:repeat="fbitem feedback">
|
||||||
|
<td tal:content="fbitem/category" />
|
||||||
|
<td tal:content="fbitem/text" />
|
||||||
|
<td tal:content="fbitem/score" />
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="button" id="show_questionnaire">
|
||||||
|
<a href="" onclick="back(); return false"
|
||||||
|
i18n:translate="">
|
||||||
|
Back to Questionnaire</a>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<div tal:define="footer item/adapted/feedbackFooter"
|
||||||
|
tal:condition="footer"
|
||||||
|
tal:content="structure python:item.renderText(footer, 'text/restructured')" />
|
||||||
|
</div>
|
||||||
|
<div id="questionnaire"
|
||||||
|
tal:condition="not:feedback">
|
||||||
|
<h3 i18n:translate="">Questionnaire</h3>
|
||||||
|
<div class="error"
|
||||||
|
tal:condition="errors">
|
||||||
|
<div tal:repeat="error errors">
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:content="error" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
<table class="listing">
|
||||||
|
<tal:qugroup repeat="qugroup item/adapted/questionGroups">
|
||||||
|
<tr><td colspan="6"> </td></tr>
|
||||||
|
<tr>
|
||||||
|
<td tal:define="infoText python:item.getInfoText(qugroup)">
|
||||||
|
<b tal:content="qugroup/title" />
|
||||||
|
<div tal:condition="infoText">
|
||||||
|
<span tal:content="structure infoText" />
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: center"
|
||||||
|
i18n:translate="">No answer</td>
|
||||||
|
<td colspan="2"
|
||||||
|
i18n:translate="">Fully applies</td>
|
||||||
|
<td colspan="2"
|
||||||
|
style="text-align: right"
|
||||||
|
i18n:translate="">Does not apply</td>
|
||||||
|
</tr>
|
||||||
|
<tr tal:repeat="question qugroup/questions">
|
||||||
|
<td tal:content="question/text" />
|
||||||
|
<td style="white-space: nowrap; text-align: center"
|
||||||
|
tal:repeat="value python:item.getValues(question)">
|
||||||
|
<input type="radio"
|
||||||
|
i18n:attributes="title"
|
||||||
|
tal:condition="value/radio"
|
||||||
|
tal:attributes="
|
||||||
|
name string:question_${question/uid};
|
||||||
|
value value/value;
|
||||||
|
checked value/checked;
|
||||||
|
title string:survey_value_${value/value}" />
|
||||||
|
<span tal:condition="not:value/radio"
|
||||||
|
title="Obligatory question, must be answered"
|
||||||
|
i18n:attributes="title">***
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tal:qugroup>
|
||||||
|
</table>
|
||||||
|
<input type="submit" name="submit" value="Evaluate Questionnaire"
|
||||||
|
i18n:attributes="value" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
|
@ -2,16 +2,31 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import unittest, doctest
|
import unittest, doctest
|
||||||
from zope.testing.doctestunit import DocFileSuite
|
|
||||||
from zope.app.testing import ztapi
|
from zope.app.testing import ztapi
|
||||||
|
from zope import component
|
||||||
from zope.interface.verify import verifyClass
|
from zope.interface.verify import verifyClass
|
||||||
|
from zope.testing.doctestunit import DocFileSuite
|
||||||
|
|
||||||
|
from loops.knowledge.qualification.base import Competence
|
||||||
|
from loops.knowledge.survey.base import Questionnaire, Question, FeedbackItem
|
||||||
|
from loops.knowledge.survey.interfaces import IQuestionnaire, IQuestion, \
|
||||||
|
IFeedbackItem
|
||||||
from loops.organize.party import Person
|
from loops.organize.party import Person
|
||||||
from loops.setup import importData as baseImportData
|
from loops.setup import importData as baseImportData
|
||||||
|
|
||||||
|
|
||||||
|
importPath = os.path.join(os.path.dirname(__file__), 'data')
|
||||||
|
|
||||||
|
|
||||||
def importData(loopsRoot):
|
def importData(loopsRoot):
|
||||||
importPath = os.path.join(os.path.dirname(__file__), 'data')
|
baseImportData(loopsRoot, importPath, 'knowledge_de.dmp')
|
||||||
baseImportData(loopsRoot, importPath, 'loops_knowledge_de.dmp')
|
|
||||||
|
def importSurvey(loopsRoot):
|
||||||
|
component.provideAdapter(Competence)
|
||||||
|
component.provideAdapter(Questionnaire, provides=IQuestionnaire)
|
||||||
|
component.provideAdapter(Question, provides=IQuestion)
|
||||||
|
component.provideAdapter(FeedbackItem, provides=IFeedbackItem)
|
||||||
|
baseImportData(loopsRoot, importPath, 'survey_de.dmp')
|
||||||
|
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
|
|
Binary file not shown.
|
@ -1,9 +1,9 @@
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
"Project-Id-Version: $Id$\n"
|
"Project-Id-Version: 0.13.0\n"
|
||||||
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
|
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
|
||||||
"PO-Revision-Date: 2012-09-17 12:00 CET\n"
|
"PO-Revision-Date: 2013-03-21 12:00 CET\n"
|
||||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -170,6 +170,105 @@ msgstr "Glossareintrag anlegen..."
|
||||||
msgid "Create Glossary Item"
|
msgid "Create Glossary Item"
|
||||||
msgstr "Glossareintrag anlegen."
|
msgstr "Glossareintrag anlegen."
|
||||||
|
|
||||||
|
# survey / questionnaire
|
||||||
|
|
||||||
|
msgid "Answer Range"
|
||||||
|
msgstr "Abstufung Bewertungen"
|
||||||
|
|
||||||
|
msgid "Feedback Footer"
|
||||||
|
msgstr "Auswertungs-Hinweis"
|
||||||
|
|
||||||
|
msgid "Text that will appear at the end of the feedback page."
|
||||||
|
msgstr "Text, der am Ende der Auswertungsseite erscheinen soll."
|
||||||
|
|
||||||
|
msgid "Number of items (answer options) to select from."
|
||||||
|
msgstr "Anzahl der Abstufungen, aus denen bei der Antwort gewählt werden kann."
|
||||||
|
|
||||||
|
msgid "Minimum Number of Answers"
|
||||||
|
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 "Required"
|
||||||
|
msgstr "Pflichtfrage"
|
||||||
|
|
||||||
|
msgid "Question must be answered."
|
||||||
|
msgstr "Frage muss unbedingt beantwortet werden."
|
||||||
|
|
||||||
|
msgid "Negative"
|
||||||
|
msgstr "Negative Polarität"
|
||||||
|
|
||||||
|
msgid "Value inversion: High selection means low value."
|
||||||
|
msgstr "Invertierung der Bewertung: Hohe gewählte Stufe bedeutet niedriger Wert."
|
||||||
|
|
||||||
|
msgid "Questionnaire"
|
||||||
|
msgstr "Fragebogen"
|
||||||
|
|
||||||
|
msgid "Feedback"
|
||||||
|
msgstr "Auswertung"
|
||||||
|
|
||||||
|
msgid "Category"
|
||||||
|
msgstr "Kategorie"
|
||||||
|
|
||||||
|
msgid "Response"
|
||||||
|
msgstr "Beurteilung"
|
||||||
|
|
||||||
|
msgid "No answer"
|
||||||
|
msgstr "Keine Antwort"
|
||||||
|
|
||||||
|
msgid "Does not apply"
|
||||||
|
msgstr "Trifft nicht zu"
|
||||||
|
|
||||||
|
msgid "Fully applies"
|
||||||
|
msgstr "Trifft voll zu"
|
||||||
|
|
||||||
|
msgid "survey_value_none"
|
||||||
|
msgstr "Keine Antwort"
|
||||||
|
|
||||||
|
msgid "survey_value_0"
|
||||||
|
msgstr "Trifft für unser Unternehmen überhaupt nicht zu"
|
||||||
|
|
||||||
|
msgid "survey_value_1"
|
||||||
|
msgstr "Trifft eher nicht zu"
|
||||||
|
|
||||||
|
msgid "survey_value_2"
|
||||||
|
msgstr "Trifft eher zu"
|
||||||
|
|
||||||
|
msgid "survey_value_3"
|
||||||
|
msgstr "Trifft für unser Unternehmen voll und ganz zu"
|
||||||
|
|
||||||
|
msgid "Evaluate Questionnaire"
|
||||||
|
msgstr "Fragebogen auswerten"
|
||||||
|
|
||||||
|
msgid "Back to Questionnaire"
|
||||||
|
msgstr "Zurück zum Fragebogen"
|
||||||
|
|
||||||
|
msgid "Please answer at least $minAnswers questions."
|
||||||
|
msgstr "Bitte beantworten Sie mindestens $minAnswers Fragen."
|
||||||
|
|
||||||
|
msgid "Please answer all questions."
|
||||||
|
msgstr "Bitte beantworten Sie alle Fragen."
|
||||||
|
|
||||||
|
msgid "Please answer the obligatory questions."
|
||||||
|
msgstr "Bitte beantworten Sie die Pflichtfragen."
|
||||||
|
|
||||||
|
msgid "Please answer the minimum number of questions."
|
||||||
|
msgstr "Bitte beantworten Sie die angegebene Mindestanzahl an Fragen je Fragengruppe."
|
||||||
|
|
||||||
|
msgid "Obligatory question, must be answered"
|
||||||
|
msgstr "Pflichtfrage, muss beantwortet werden"
|
||||||
|
|
||||||
|
# competence (qualification)
|
||||||
|
|
||||||
|
msgid "Validity Period (Months)"
|
||||||
|
msgstr "Gültigkeitszeitraum (Monate)"
|
||||||
|
|
||||||
|
msgid "Number of months the competence remains valid. Zero means unlimited validity."
|
||||||
|
msgstr "Anzahl der Monate, die diese Kompetenz gültig bleibt. Null bedeutet unbegrenzte Gültigkeit"
|
||||||
|
|
||||||
|
# organize
|
||||||
|
|
||||||
msgid "Create Person..."
|
msgid "Create Person..."
|
||||||
msgstr "Person anlegen..."
|
msgstr "Person anlegen..."
|
||||||
|
|
||||||
|
@ -212,6 +311,8 @@ msgstr "Adresse bearbeiten..."
|
||||||
msgid "Modify address."
|
msgid "Modify address."
|
||||||
msgstr "Adresse bearbeiten."
|
msgstr "Adresse bearbeiten."
|
||||||
|
|
||||||
|
# general
|
||||||
|
|
||||||
msgid "Create Concept, Type = "
|
msgid "Create Concept, Type = "
|
||||||
msgstr "Begriff anlegen, Typ = "
|
msgstr "Begriff anlegen, Typ = "
|
||||||
|
|
||||||
|
@ -230,6 +331,8 @@ msgstr "Diese Ressource bearbeiten."
|
||||||
msgid "Edit Concept"
|
msgid "Edit Concept"
|
||||||
msgstr "Begriff bearbeiten"
|
msgstr "Begriff bearbeiten"
|
||||||
|
|
||||||
|
# events and tasks
|
||||||
|
|
||||||
msgid "Create Event..."
|
msgid "Create Event..."
|
||||||
msgstr "Termin anlegen..."
|
msgstr "Termin anlegen..."
|
||||||
|
|
||||||
|
@ -443,9 +546,15 @@ msgstr "Überschrift, sprechende Bezeichnung des Begriffs"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Beschreibung"
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
|
msgid "label_description"
|
||||||
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
msgid "A medium-length description describing the content and the purpose of the object"
|
msgid "A medium-length description describing the content and the purpose of the object"
|
||||||
msgstr "Eine ausführlichere Beschreibung des Inhalts und Zwecks des Objekts"
|
msgstr "Eine ausführlichere Beschreibung des Inhalts und Zwecks des Objekts"
|
||||||
|
|
||||||
|
msgid "desc_description"
|
||||||
|
msgstr "Eine ausführlichere Beschreibung des Inhalts und Zwecks des Objekts"
|
||||||
|
|
||||||
msgid "Related Items"
|
msgid "Related Items"
|
||||||
msgstr "Verwandte Begriffe"
|
msgstr "Verwandte Begriffe"
|
||||||
|
|
||||||
|
@ -707,9 +816,6 @@ msgstr "Deckungsgrad"
|
||||||
msgid "Create loops Note"
|
msgid "Create loops Note"
|
||||||
msgstr "loops-Notiz anlegen"
|
msgstr "loops-Notiz anlegen"
|
||||||
|
|
||||||
msgid "State information for $definition: $title"
|
|
||||||
msgstr "Status ($definition): $title"
|
|
||||||
|
|
||||||
msgid "User ID"
|
msgid "User ID"
|
||||||
msgstr "Benutzerkennung"
|
msgstr "Benutzerkennung"
|
||||||
|
|
||||||
|
@ -886,65 +992,178 @@ msgstr "an"
|
||||||
msgid "move_to_task"
|
msgid "move_to_task"
|
||||||
msgstr "nach"
|
msgstr "nach"
|
||||||
|
|
||||||
|
# state definitions
|
||||||
|
|
||||||
|
msgid "Show all"
|
||||||
|
msgstr "Alle anzeigen"
|
||||||
|
|
||||||
|
msgid "Restrict to objects with certain states"
|
||||||
|
msgstr "Auf Objekte mit bestimmtem Status beschränken"
|
||||||
|
|
||||||
|
msgid "Workflow"
|
||||||
|
msgstr "Statusdefinition/Workflow"
|
||||||
|
|
||||||
|
msgid "States"
|
||||||
|
msgstr "Statuswerte"
|
||||||
|
|
||||||
|
msgid "State information for $definition: $title"
|
||||||
|
msgstr "Status ($definition): $title"
|
||||||
|
|
||||||
|
msgid "classification_quality"
|
||||||
|
msgstr "Klassifizierung"
|
||||||
|
|
||||||
|
msgid "simple_publishing"
|
||||||
|
msgstr "Veröffentlichung"
|
||||||
|
|
||||||
|
msgid "task_states"
|
||||||
|
msgstr "Aufgabe"
|
||||||
|
|
||||||
|
msgid "publishable_task"
|
||||||
|
msgstr "Aufgabe/Zugriff"
|
||||||
|
|
||||||
|
# state names
|
||||||
|
|
||||||
|
msgid "accepted"
|
||||||
|
msgstr "angenommen"
|
||||||
|
|
||||||
|
msgid "active"
|
||||||
|
msgstr "aktiv"
|
||||||
|
|
||||||
|
msgid "active (published)"
|
||||||
|
msgstr "aktiv (zugänglich)"
|
||||||
|
|
||||||
|
msgid "archived"
|
||||||
|
msgstr "Archiv"
|
||||||
|
|
||||||
|
msgid "cancelled"
|
||||||
|
msgstr "abgebrochen"
|
||||||
|
|
||||||
|
msgid "classified"
|
||||||
|
msgstr "klassifiziert"
|
||||||
|
|
||||||
|
msgid "closed"
|
||||||
|
msgstr "abgeschlossen"
|
||||||
|
|
||||||
|
msgid "delegated"
|
||||||
|
msgstr "delegiert"
|
||||||
|
|
||||||
|
msgid "done"
|
||||||
|
msgstr "bearbeitet"
|
||||||
|
|
||||||
|
msgid "draft"
|
||||||
|
msgstr "Entwurf"
|
||||||
|
|
||||||
|
msgid "finished"
|
||||||
|
msgstr "erledigt"
|
||||||
|
|
||||||
|
msgid "finished (published)"
|
||||||
|
msgstr "erledigt (zugänglich)"
|
||||||
|
|
||||||
|
msgid "moved"
|
||||||
|
msgstr "verschoben"
|
||||||
|
|
||||||
msgid "new"
|
msgid "new"
|
||||||
msgstr "neu"
|
msgstr "neu"
|
||||||
|
|
||||||
msgid "planned"
|
msgid "planned"
|
||||||
msgstr "geplant"
|
msgstr "geplant"
|
||||||
|
|
||||||
msgid "accepted"
|
msgid "private"
|
||||||
msgstr "angenommen"
|
msgstr "privat"
|
||||||
|
|
||||||
msgid "delegated"
|
msgid "published"
|
||||||
msgstr "delegiert"
|
msgstr "veröffentlicht"
|
||||||
|
|
||||||
msgid "running"
|
msgid "removed"
|
||||||
msgstr "in Arbeit"
|
msgstr "gelöscht"
|
||||||
|
|
||||||
msgid "done"
|
|
||||||
msgstr "bearbeitet"
|
|
||||||
|
|
||||||
msgid "finished"
|
|
||||||
msgstr "beendet"
|
|
||||||
|
|
||||||
msgid "closed"
|
|
||||||
msgstr "abgeschlossen"
|
|
||||||
|
|
||||||
msgid "cancelled"
|
|
||||||
msgstr "abgebrochen"
|
|
||||||
|
|
||||||
msgid "moved"
|
|
||||||
msgstr "verschoben"
|
|
||||||
|
|
||||||
msgid "replaced"
|
msgid "replaced"
|
||||||
msgstr "ersetzt"
|
msgstr "ersetzt"
|
||||||
|
|
||||||
msgid "plan"
|
msgid "running"
|
||||||
msgstr "planen"
|
msgstr "in Arbeit"
|
||||||
|
|
||||||
|
msgid "unclassified"
|
||||||
|
msgstr "unklassifiziert"
|
||||||
|
|
||||||
|
msgid "verified"
|
||||||
|
msgstr "verifiziert"
|
||||||
|
|
||||||
|
# transitions
|
||||||
|
|
||||||
msgid "accept"
|
msgid "accept"
|
||||||
msgstr "annehmen"
|
msgstr "annehmen"
|
||||||
|
|
||||||
msgid "start working"
|
msgid "archive"
|
||||||
msgstr "Arbeit beginnen"
|
msgstr "archivieren"
|
||||||
|
|
||||||
msgid "work"
|
msgid "classify"
|
||||||
msgstr "bearbeiten"
|
msgstr "klassifizieren"
|
||||||
|
|
||||||
msgid "finish"
|
msgid "change_classification"
|
||||||
msgstr "beenden"
|
msgstr "Klassifizierung ändern"
|
||||||
|
|
||||||
msgid "cancel"
|
msgid "cancel"
|
||||||
msgstr "abbrechen"
|
msgstr "abbrechen"
|
||||||
|
|
||||||
|
msgid "close"
|
||||||
|
msgstr "abschließen"
|
||||||
|
|
||||||
msgid "delegate"
|
msgid "delegate"
|
||||||
msgstr "delegieren"
|
msgstr "delegieren"
|
||||||
|
|
||||||
|
msgid "finish"
|
||||||
|
msgstr "beenden"
|
||||||
|
|
||||||
|
msgid "finish (published)"
|
||||||
|
msgstr "beenden (zugänglich)"
|
||||||
|
|
||||||
|
msgid "hide"
|
||||||
|
msgstr "verstecken"
|
||||||
|
|
||||||
msgid "move"
|
msgid "move"
|
||||||
msgstr "verschieben"
|
msgstr "verschieben"
|
||||||
|
|
||||||
msgid "close"
|
msgid "No change"
|
||||||
msgstr "abschließen"
|
msgstr "keine Änderung"
|
||||||
|
|
||||||
|
msgid "plan"
|
||||||
|
msgstr "planen"
|
||||||
|
|
||||||
|
msgid "publish"
|
||||||
|
msgstr "veröffentlichen"
|
||||||
|
|
||||||
|
msgid "remove"
|
||||||
|
msgstr "entfernen"
|
||||||
|
|
||||||
|
msgid "release"
|
||||||
|
msgstr "freigeben"
|
||||||
|
|
||||||
|
msgid "release, publish"
|
||||||
|
msgstr "freigeben (zugänglich)"
|
||||||
|
|
||||||
|
msgid "re-open"
|
||||||
|
msgstr "zurücksetzen"
|
||||||
|
|
||||||
|
msgid "remove_classification"
|
||||||
|
msgstr "Klassifizierung entfernen"
|
||||||
|
|
||||||
|
msgid "retract"
|
||||||
|
msgstr "einschränken"
|
||||||
|
|
||||||
|
msgid "show"
|
||||||
|
msgstr "anzeigen"
|
||||||
|
|
||||||
|
msgid "start working"
|
||||||
|
msgstr "Arbeit beginnen"
|
||||||
|
|
||||||
|
msgid "verify"
|
||||||
|
msgstr "verifizieren"
|
||||||
|
|
||||||
|
msgid "work"
|
||||||
|
msgstr "bearbeiten"
|
||||||
|
|
||||||
|
# calendar
|
||||||
|
|
||||||
msgid "Monday"
|
msgid "Monday"
|
||||||
msgstr "Montag"
|
msgstr "Montag"
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
loops - Linked Objects for Organization and Processing Services
|
loops - Linked Objects for Organization and Processing Services
|
||||||
===============================================================
|
===============================================================
|
||||||
|
|
||||||
($Id$)
|
|
||||||
|
|
||||||
Note: This packages depends on cybertools.organize.
|
Note: This packages depends on cybertools.organize.
|
||||||
|
|
||||||
Let's do some basic setup
|
Let's do some basic setup
|
||||||
|
@ -267,9 +265,9 @@ Person objects that have a user assigned to them receive this user
|
||||||
|
|
||||||
>>> from zope.securitypolicy.interfaces import IPrincipalRoleMap
|
>>> from zope.securitypolicy.interfaces import IPrincipalRoleMap
|
||||||
>>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles()
|
>>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles()
|
||||||
[('loops.Owner', 'users.john', PermissionSetting: Allow)]
|
[('loops.Person', 'users.john', PermissionSetting: Allow)]
|
||||||
>>> IPrincipalRoleMap(concepts['person.newuser']).getPrincipalsAndRoles()
|
>>> IPrincipalRoleMap(concepts['person.newuser']).getPrincipalsAndRoles()
|
||||||
[('loops.Owner', u'loops.newuser', PermissionSetting: Allow)]
|
[('loops.Person', u'loops.newuser', PermissionSetting: Allow)]
|
||||||
|
|
||||||
The person ``martha`` hasn't got a user id, so there is no role assigned
|
The person ``martha`` hasn't got a user id, so there is no role assigned
|
||||||
to it.
|
to it.
|
||||||
|
@ -307,9 +305,12 @@ Now we are ready to look for the real stuff - what John is allowed to do.
|
||||||
True
|
True
|
||||||
|
|
||||||
Person objects that have an owner may be modified by this owner.
|
Person objects that have an owner may be modified by this owner.
|
||||||
|
(Changed in 2013-01-14: Owner not set automatically)
|
||||||
|
|
||||||
>>> canWrite(john, 'title')
|
>>> canWrite(john, 'title')
|
||||||
True
|
False
|
||||||
|
|
||||||
|
was: True
|
||||||
|
|
||||||
So let's try with another user with another role setting.
|
So let's try with another user with another role setting.
|
||||||
|
|
||||||
|
@ -409,7 +410,7 @@ Send Email to Members
|
||||||
>>> form.subject
|
>>> form.subject
|
||||||
u"loops Notification from '$site'"
|
u"loops Notification from '$site'"
|
||||||
>>> form.mailBody
|
>>> form.mailBody
|
||||||
u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.116\n\n'
|
u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.113\n\n'
|
||||||
|
|
||||||
|
|
||||||
Show Presence of Other Users
|
Show Presence of Other Users
|
||||||
|
|
|
@ -56,6 +56,7 @@ actions.register('createEvent', 'portlet', DialogAction,
|
||||||
typeToken='.loops/concepts/event',
|
typeToken='.loops/concepts/event',
|
||||||
fixedType=True,
|
fixedType=True,
|
||||||
prerequisites=['registerDojoDateWidget'],
|
prerequisites=['registerDojoDateWidget'],
|
||||||
|
permission='loops.AssignAsParent',
|
||||||
)
|
)
|
||||||
|
|
||||||
actions.register('editEvent', 'portlet', DialogAction,
|
actions.register('editEvent', 'portlet', DialogAction,
|
||||||
|
@ -383,8 +384,9 @@ class CreateFollowUpEvent(CreateConcept, BaseFollowUpController):
|
||||||
bevt = baseObject(self.baseEvent)
|
bevt = baseObject(self.baseEvent)
|
||||||
bevt.assignChild(obj, self.followsPredicate)
|
bevt.assignChild(obj, self.followsPredicate)
|
||||||
for rel in bevt.getParentRelations():
|
for rel in bevt.getParentRelations():
|
||||||
if rel.predicate != self.view.typePredicate:
|
if rel.predicate not in (self.view.typePredicate, self.followsPredicate):
|
||||||
obj.assignParent(rel.first, rel.predicate)
|
obj.assignParent(rel.first, rel.predicate,
|
||||||
|
order=rel.order, relevance=rel.relevance)
|
||||||
|
|
||||||
|
|
||||||
class EditFollowUpEvent(EditConcept, BaseFollowUpController):
|
class EditFollowUpEvent(EditConcept, BaseFollowUpController):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -31,6 +31,7 @@ from cybertools.organize.interfaces import IAddress as IBaseAddress
|
||||||
from cybertools.organize.interfaces import IPerson as IBasePerson
|
from cybertools.organize.interfaces import IPerson as IBasePerson
|
||||||
from cybertools.organize.interfaces import ITask
|
from cybertools.organize.interfaces import ITask
|
||||||
from loops.interfaces import ILoopsAdapter, IConceptSchema, IRelationAdapter
|
from loops.interfaces import ILoopsAdapter, IConceptSchema, IRelationAdapter
|
||||||
|
from loops.interfaces import HtmlText
|
||||||
from loops.organize.util import getPrincipalFolder
|
from loops.organize.util import getPrincipalFolder
|
||||||
from loops import util
|
from loops import util
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
@ -173,26 +174,36 @@ class IEvent(ITask):
|
||||||
|
|
||||||
class IAgendaItem(ILoopsAdapter):
|
class IAgendaItem(ILoopsAdapter):
|
||||||
|
|
||||||
|
description = HtmlText(
|
||||||
|
title=_(u'label_description'),
|
||||||
|
description=_(u'desc_description'),
|
||||||
|
default=u'',
|
||||||
|
missing_value=u'',
|
||||||
|
required=False)
|
||||||
|
|
||||||
responsible = schema.TextLine(
|
responsible = schema.TextLine(
|
||||||
title=_(u'label_responsible'),
|
title=_(u'label_responsible'),
|
||||||
description=_(u'desc_responsible.'),
|
description=_(u'desc_responsible'),
|
||||||
default=u'',
|
default=u'',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
discussion = schema.Text(
|
discussion = HtmlText(
|
||||||
title=_(u'label_discussion'),
|
title=_(u'label_discussion'),
|
||||||
description=_(u'desc_discussion.'),
|
description=_(u'desc_discussion'),
|
||||||
default=u'',
|
default=u'',
|
||||||
missing_value=u'',
|
missing_value=u'',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
consequences = schema.Text(
|
consequences = HtmlText(
|
||||||
title=_(u'label_consequences'),
|
title=_(u'label_consequences'),
|
||||||
description=_(u'desc_consequences.'),
|
description=_(u'desc_consequences'),
|
||||||
default=u'',
|
default=u'',
|
||||||
missing_value=u'',
|
missing_value=u'',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
|
description.height = 10
|
||||||
|
discussion.height = consequences.height = 7
|
||||||
|
|
||||||
|
|
||||||
# 'hasrole' predicate
|
# 'hasrole' predicate
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -41,9 +41,11 @@ from loops.interfaces import IConcept
|
||||||
from loops.organize.interfaces import IAddress, IPerson, IHasRole
|
from loops.organize.interfaces import IAddress, IPerson, IHasRole
|
||||||
from loops.organize.interfaces import ANNOTATION_KEY
|
from loops.organize.interfaces import ANNOTATION_KEY
|
||||||
from loops.predicate import RelationAdapter
|
from loops.predicate import RelationAdapter
|
||||||
from loops.security.common import assignOwner, removeOwner, allowEditingForOwner
|
|
||||||
from loops.type import TypeInterfaceSourceList
|
|
||||||
from loops.predicate import PredicateInterfaceSourceList
|
from loops.predicate import PredicateInterfaceSourceList
|
||||||
|
from loops.security.common import assignOwner, removeOwner, allowEditingForOwner
|
||||||
|
from loops.security.common import assignPersonRole, removePersonRole
|
||||||
|
from loops.security.interfaces import ISecuritySetter
|
||||||
|
from loops.type import TypeInterfaceSourceList
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +84,7 @@ class Person(AdapterBase, BasePerson):
|
||||||
def getUserId(self):
|
def getUserId(self):
|
||||||
return getattr(self.context, '_userId', None)
|
return getattr(self.context, '_userId', None)
|
||||||
def setUserId(self, userId):
|
def setUserId(self, userId):
|
||||||
|
setter = ISecuritySetter(self)
|
||||||
if userId:
|
if userId:
|
||||||
principal = self.getPrincipalForUserId(userId)
|
principal = self.getPrincipalForUserId(userId)
|
||||||
if principal is None:
|
if principal is None:
|
||||||
|
@ -97,13 +100,16 @@ class Person(AdapterBase, BasePerson):
|
||||||
if ann is None: # or not isinstance(ann, PersistentMapping):
|
if ann is None: # or not isinstance(ann, PersistentMapping):
|
||||||
ann = pa[ANNOTATION_KEY] = PersistentMapping()
|
ann = pa[ANNOTATION_KEY] = PersistentMapping()
|
||||||
ann[loopsId] = self.context
|
ann[loopsId] = self.context
|
||||||
assignOwner(self.context, userId)
|
#assignOwner(self.context, userId)
|
||||||
|
assignPersonRole(self.context, userId)
|
||||||
oldUserId = self.userId
|
oldUserId = self.userId
|
||||||
if oldUserId and oldUserId != userId:
|
if oldUserId and oldUserId != userId:
|
||||||
self.removeReferenceFromPrincipal(oldUserId)
|
self.removeReferenceFromPrincipal(oldUserId)
|
||||||
removeOwner(self.context, oldUserId)
|
removeOwner(self.context, oldUserId)
|
||||||
|
removePersonRole(self.context, oldUserId)
|
||||||
self.context._userId = userId
|
self.context._userId = userId
|
||||||
allowEditingForOwner(self.context, revert=not userId)
|
setter.propagateSecurity()
|
||||||
|
allowEditingForOwner(self.context, revert=not userId) # why this?
|
||||||
userId = property(getUserId, setUserId)
|
userId = property(getUserId, setUserId)
|
||||||
|
|
||||||
def removeReferenceFromPrincipal(self, userId):
|
def removeReferenceFromPrincipal(self, userId):
|
||||||
|
|
|
@ -27,6 +27,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
|
||||||
from cybertools.browser.configurator import ViewConfigurator, MacroViewProperty
|
from cybertools.browser.configurator import ViewConfigurator, MacroViewProperty
|
||||||
|
from cybertools.stateful.interfaces import IStateful
|
||||||
from loops.browser.node import NodeView
|
from loops.browser.node import NodeView
|
||||||
from loops.concept import Concept
|
from loops.concept import Concept
|
||||||
from loops.organize.party import getPersonForUser
|
from loops.organize.party import getPersonForUser
|
||||||
|
@ -107,7 +108,30 @@ class FilterView(NodeView):
|
||||||
result.setdefault(obj.getType(), set([])).add(obj)
|
result.setdefault(obj.getType(), set([])).add(obj)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def check(self, obj):
|
def checkOptions(self, obj, options):
|
||||||
|
if isinstance(options, list):
|
||||||
|
return True
|
||||||
|
form = self.request.form
|
||||||
|
if form.get('filter.states') == 'all':
|
||||||
|
return True
|
||||||
|
filterStates = options
|
||||||
|
for std in filterStates.keys():
|
||||||
|
formStates = form.get('filter.states.' + std)
|
||||||
|
if formStates == 'all':
|
||||||
|
continue
|
||||||
|
stf = component.getAdapter(obj, IStateful, name=std)
|
||||||
|
if formStates:
|
||||||
|
if stf.state not in formStates.split(','):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if stf.state not in getattr(filterStates, std):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check(self, obj, options=None):
|
||||||
|
if options is not None:
|
||||||
|
if not self.checkOptions(obj, options):
|
||||||
|
return False
|
||||||
fs = self.filterStructure
|
fs = self.filterStructure
|
||||||
if not fs:
|
if not fs:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
loops - Linked Objects for Organization and Processing Services
|
loops - Linked Objects for Organization and Processing Services
|
||||||
===============================================================
|
===============================================================
|
||||||
|
|
||||||
($Id$)
|
|
||||||
|
|
||||||
>>> from zope import component
|
>>> from zope import component
|
||||||
>>> from zope.traversing.api import getName
|
>>> from zope.traversing.api import getName
|
||||||
|
|
||||||
|
@ -183,6 +181,12 @@ Querying objects by state
|
||||||
[<...>]
|
[<...>]
|
||||||
|
|
||||||
|
|
||||||
|
Task States
|
||||||
|
===========
|
||||||
|
|
||||||
|
>>> from loops.organize.stateful.task import taskStates, publishableTask
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,13 +18,12 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Views and actions for states management.
|
Views and actions for states management.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.i18n import translate
|
||||||
|
|
||||||
from cybertools.browser.action import Action, actions
|
from cybertools.browser.action import Action, actions
|
||||||
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
||||||
|
@ -36,9 +35,12 @@ from loops.security.common import checkPermission
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
template = ViewPageTemplateFile('view_macros.pt')
|
||||||
|
|
||||||
statefulActions = ('classification_quality',
|
statefulActions = ('classification_quality',
|
||||||
'simple_publishing',
|
'simple_publishing',
|
||||||
'task_states',)
|
'task_states',
|
||||||
|
'publishable_task',)
|
||||||
|
|
||||||
|
|
||||||
class StateAction(Action):
|
class StateAction(Action):
|
||||||
|
@ -53,9 +55,11 @@ class StateAction(Action):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def description(self):
|
def description(self):
|
||||||
|
lang = self.view.languageInfo.language
|
||||||
|
definition = translate(_(self.definition), target_language=lang)
|
||||||
|
title = translate(_(self.stateObject.title), target_language=lang)
|
||||||
return _(u'State information for $definition: $title',
|
return _(u'State information for $definition: $title',
|
||||||
mapping=dict(definition=self.definition,
|
mapping=dict(definition=definition, title=title))
|
||||||
title=self.stateObject.title))
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def stateObject(self):
|
def stateObject(self):
|
||||||
|
@ -77,7 +81,7 @@ for std in statefulActions:
|
||||||
#class StateQuery(ConceptView):
|
#class StateQuery(ConceptView):
|
||||||
class StateQuery(BaseView):
|
class StateQuery(BaseView):
|
||||||
|
|
||||||
template = ViewPageTemplateFile('view_macros.pt')
|
template = template
|
||||||
|
|
||||||
form_action = 'execute_search_action'
|
form_action = 'execute_search_action'
|
||||||
|
|
||||||
|
@ -144,3 +148,15 @@ class StateQuery(BaseView):
|
||||||
uids = q.apply()
|
uids = q.apply()
|
||||||
return self.viewIterator(getObjects(uids, self.loopsRoot))
|
return self.viewIterator(getObjects(uids, self.loopsRoot))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class FilterAllStates(BaseView):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macros(self):
|
||||||
|
return template.macros
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return self.macros['filter_allstates']
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.organize.stateful.base.SimplePublishable"
|
factory="loops.organize.stateful.base.SimplePublishable"
|
||||||
name="simple_publishing" trusted="True" />
|
name="simple_publishing" />
|
||||||
<zope:class class="loops.organize.stateful.base.SimplePublishable">
|
<zope:class class="loops.organize.stateful.base.SimplePublishable">
|
||||||
<require permission="zope.View"
|
<require permission="zope.View"
|
||||||
interface="cybertools.stateful.interfaces.IStateful" />
|
interface="cybertools.stateful.interfaces.IStateful" />
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.organize.stateful.task.StatefulTask"
|
factory="loops.organize.stateful.task.StatefulTask"
|
||||||
name="task_states" trusted="True" />
|
name="task_states" />
|
||||||
<zope:class class="loops.organize.stateful.task.StatefulTask">
|
<zope:class class="loops.organize.stateful.task.StatefulTask">
|
||||||
<require permission="zope.View"
|
<require permission="zope.View"
|
||||||
interface="cybertools.stateful.interfaces.IStateful" />
|
interface="cybertools.stateful.interfaces.IStateful" />
|
||||||
|
@ -49,13 +49,27 @@
|
||||||
set_schema="cybertools.stateful.interfaces.IStateful" />
|
set_schema="cybertools.stateful.interfaces.IStateful" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
|
<zope:utility
|
||||||
|
factory="loops.organize.stateful.task.publishableTask"
|
||||||
|
name="publishable_task" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
factory="loops.organize.stateful.task.PublishableTask"
|
||||||
|
name="publishable_task" />
|
||||||
|
<zope:class class="loops.organize.stateful.task.PublishableTask">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="cybertools.stateful.interfaces.IStateful" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="cybertools.stateful.interfaces.IStateful" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<zope:utility
|
<zope:utility
|
||||||
factory="loops.organize.stateful.quality.classificationQuality"
|
factory="loops.organize.stateful.quality.classificationQuality"
|
||||||
name="classification_quality" />
|
name="classification_quality" />
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
factory="loops.organize.stateful.quality.ClassificationQualityCheckable"
|
factory="loops.organize.stateful.quality.ClassificationQualityCheckable"
|
||||||
name="classification_quality" trusted="True" />
|
name="classification_quality" />
|
||||||
<zope:class class="loops.organize.stateful.quality.ClassificationQualityCheckable">
|
<zope:class class="loops.organize.stateful.quality.ClassificationQualityCheckable">
|
||||||
<require permission="zope.View"
|
<require permission="zope.View"
|
||||||
interface="cybertools.stateful.interfaces.IStateful" />
|
interface="cybertools.stateful.interfaces.IStateful" />
|
||||||
|
@ -71,6 +85,12 @@
|
||||||
class="loops.organize.stateful.browser.StateQuery"
|
class="loops.organize.stateful.browser.StateQuery"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
for="loops.interfaces.IConcept"
|
||||||
|
name="filter_input.allstates"
|
||||||
|
class="loops.organize.stateful.browser.FilterAllStates"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
<!-- event handlers -->
|
<!-- event handlers -->
|
||||||
|
|
||||||
<zope:subscriber handler="loops.organize.stateful.base.handleTransition" />
|
<zope:subscriber handler="loops.organize.stateful.base.handleTransition" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
Basic implementations for stateful objects and adapters.
|
Basic implementations for stateful objects and adapters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from zope.app.security.settings import Allow, Deny, Unset
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.component import adapter
|
from zope.component import adapter
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
|
@ -30,24 +31,107 @@ from cybertools.stateful.definition import State, Transition
|
||||||
from cybertools.stateful.interfaces import IStatesDefinition, IStateful
|
from cybertools.stateful.interfaces import IStatesDefinition, IStateful
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.organize.stateful.base import StatefulLoopsObject
|
from loops.organize.stateful.base import StatefulLoopsObject
|
||||||
|
from loops.security.interfaces import ISecuritySetter
|
||||||
|
|
||||||
|
|
||||||
|
def setPermissionsForRoles(settings):
|
||||||
|
def setSecurity(obj):
|
||||||
|
setter = ISecuritySetter(obj.context)
|
||||||
|
setter.setRolePermissions(settings)
|
||||||
|
setter.propagateSecurity()
|
||||||
|
return setSecurity
|
||||||
|
|
||||||
|
|
||||||
@implementer(IStatesDefinition)
|
@implementer(IStatesDefinition)
|
||||||
def taskStates():
|
def taskStates():
|
||||||
return StatesDefinition('task_states',
|
return StatesDefinition('task_states',
|
||||||
State('planned', 'planned', ('finish', 'cancel'),
|
State('draft', 'draft', ('release', 'cancel',),
|
||||||
|
color='blue'),
|
||||||
|
State('active', 'active', ('finish', 'cancel',),
|
||||||
color='yellow'),
|
color='yellow'),
|
||||||
State('finished', 'finished', ('reopen'),
|
State('finished', 'finished', ('reopen', 'archive',),
|
||||||
color='green'),
|
color='green'),
|
||||||
State('cancelled', 'cancelled', ('reopen'),
|
State('cancelled', 'cancelled', ('reopen',),
|
||||||
|
color='x'),
|
||||||
|
State('archived', 'archived', ('reopen',),
|
||||||
color='grey'),
|
color='grey'),
|
||||||
|
Transition('release', 'release', 'active'),
|
||||||
Transition('finish', 'finish', 'finished'),
|
Transition('finish', 'finish', 'finished'),
|
||||||
Transition('cancel', 'cancel', 'cancelled'),
|
Transition('cancel', 'cancel', 'cancelled'),
|
||||||
Transition('reopen', 're-open', 'planned'),
|
Transition('reopen', 're-open', 'draft'),
|
||||||
initialState='planned')
|
initialState='draft')
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(IStatesDefinition)
|
||||||
|
def publishableTask():
|
||||||
|
return StatesDefinition('publishable_task',
|
||||||
|
State('draft', 'draft', ('release', 'release_publish', 'cancel',),
|
||||||
|
color='yellow',
|
||||||
|
setSecurity=setPermissionsForRoles({
|
||||||
|
('zope.View', 'zope.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Person'): Deny,
|
||||||
|
('zope.View', 'loops.Staff'): Deny,})),
|
||||||
|
State('active', 'active', ('reopen', 'finish', 'publish', 'cancel',),
|
||||||
|
color='lightblue',
|
||||||
|
setSecurity=setPermissionsForRoles({
|
||||||
|
('zope.View', 'zope.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Person'): Allow,
|
||||||
|
('zope.View', 'loops.Staff'): Deny,})),
|
||||||
|
State('active_published', 'active (published)',
|
||||||
|
('reopen', 'finish_published', 'retract', 'cancel',), color='blue',
|
||||||
|
setSecurity=setPermissionsForRoles({
|
||||||
|
('zope.View', 'zope.Member'): Allow,
|
||||||
|
('zope.View', 'loops.Member'): Allow,
|
||||||
|
('zope.View', 'loops.Person'): Allow,
|
||||||
|
('zope.View', 'loops.Staff'): Allow,})),
|
||||||
|
State('finished', 'finished', ('reopen', 'archive',),
|
||||||
|
color='lightgreen',
|
||||||
|
setSecurity=setPermissionsForRoles({
|
||||||
|
('zope.View', 'zope.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Person'): Allow,
|
||||||
|
('zope.View', 'loops.Staff'): Deny,})),
|
||||||
|
State('finished_published', 'finished (published)', ('reopen', 'archive',),
|
||||||
|
color='green',
|
||||||
|
setSecurity=setPermissionsForRoles({
|
||||||
|
('zope.View', 'zope.Member'): Allow,
|
||||||
|
('zope.View', 'loops.Member'): Allow,
|
||||||
|
('zope.View', 'loops.Person'): Allow,
|
||||||
|
('zope.View', 'loops.Staff'): Allow,})),
|
||||||
|
State('cancelled', 'cancelled', ('reopen',),
|
||||||
|
color='x',
|
||||||
|
setSecurity=setPermissionsForRoles({
|
||||||
|
('zope.View', 'zope.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Person'): Deny,
|
||||||
|
('zope.View', 'loops.Staff'): Deny,})),
|
||||||
|
State('archived', 'archived', ('reopen',),
|
||||||
|
color='grey',
|
||||||
|
setSecurity=setPermissionsForRoles({
|
||||||
|
('zope.View', 'zope.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Member'): Deny,
|
||||||
|
('zope.View', 'loops.Person'): Deny,
|
||||||
|
('zope.View', 'loops.Staff'): Deny,})),
|
||||||
|
Transition('release', 'release', 'active'),
|
||||||
|
Transition('release_publish', 'release, publish', 'active_published'),
|
||||||
|
Transition('publish', 'publish', 'active_published'),
|
||||||
|
Transition('retract', 'retract', 'draft'),
|
||||||
|
Transition('finish', 'finish', 'finished'),
|
||||||
|
Transition('finish_published', 'finish (published)', 'finished_published'),
|
||||||
|
Transition('cancel', 'cancel', 'cancelled'),
|
||||||
|
Transition('reopen', 're-open', 'draft'),
|
||||||
|
Transition('archive', 'archive', 'archived'),
|
||||||
|
initialState='draft')
|
||||||
|
|
||||||
|
|
||||||
class StatefulTask(StatefulLoopsObject):
|
class StatefulTask(StatefulLoopsObject):
|
||||||
|
|
||||||
statesDefinition = 'task_states'
|
statesDefinition = 'task_states'
|
||||||
|
|
||||||
|
|
||||||
|
class PublishableTask(StatefulLoopsObject):
|
||||||
|
|
||||||
|
statesDefinition = 'publishable_task'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
<!-- $Id$ -->
|
<html i18n:domain="loops">
|
||||||
|
|
||||||
|
|
||||||
|
<metal:input define-macro="filter_allstates">
|
||||||
|
<input type="checkbox" name="filter.states" value="all" id="filter-states"
|
||||||
|
onclick="submit()"
|
||||||
|
tal:attributes="checked python:
|
||||||
|
request.form.get('filter.states') == 'all'" />
|
||||||
|
<label for="filter-states"
|
||||||
|
i18n:translate="">Show all</label>
|
||||||
|
</metal:input>
|
||||||
|
|
||||||
|
|
||||||
<metal:query define-macro="query">
|
<metal:query define-macro="query">
|
||||||
|
@ -56,3 +66,6 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</metal:query>
|
</metal:query>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Base class(es) for track/record managers.
|
Base class(es) for track/record managers.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
@ -46,6 +44,10 @@ class BaseRecordManager(object):
|
||||||
def loopsRoot(self):
|
def loopsRoot(self):
|
||||||
return self.context.getLoopsRoot()
|
return self.context.getLoopsRoot()
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def uid(self):
|
||||||
|
return util.getUidForObject(self.context)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def storage(self):
|
def storage(self):
|
||||||
records = self.loopsRoot.getRecordManager()
|
records = self.loopsRoot.getRecordManager()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,9 +17,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
View class(es) for change tracks.
|
View classes for tracks.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope import component
|
from zope import component
|
||||||
|
|
|
@ -151,6 +151,7 @@ class WorkItemDetails(TrackDetails):
|
||||||
addParams=dict(id=self.track.__name__))
|
addParams=dict(id=self.track.__name__))
|
||||||
actions = [info, WorkItemStateAction(self)]
|
actions = [info, WorkItemStateAction(self)]
|
||||||
if self.isLastInRun and self.allowedToEditWorkItem:
|
if self.isLastInRun and self.allowedToEditWorkItem:
|
||||||
|
#if self.allowedToEditWorkItem:
|
||||||
self.view.registerDojoDateWidget()
|
self.view.registerDojoDateWidget()
|
||||||
self.view.registerDojoNumberWidget()
|
self.view.registerDojoNumberWidget()
|
||||||
self.view.registerDojoTextarea()
|
self.view.registerDojoTextarea()
|
||||||
|
@ -167,7 +168,10 @@ class WorkItemDetails(TrackDetails):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def allowedToEditWorkItem(self):
|
def allowedToEditWorkItem(self):
|
||||||
|
# if not canAccessObject(self.object.task):
|
||||||
|
# return False
|
||||||
if checkPermission('loops.ManageSite', self.object):
|
if checkPermission('loops.ManageSite', self.object):
|
||||||
|
# or hasRole('loops.Master', self.object):
|
||||||
return True
|
return True
|
||||||
if self.track.data.get('creator') == self.personId:
|
if self.track.data.get('creator') == self.personId:
|
||||||
return True
|
return True
|
||||||
|
@ -288,6 +292,25 @@ class TaskWorkItems(BaseWorkItemsView, ConceptView):
|
||||||
return sorted(self.query(**criteria), key=lambda x: x.track.timeStamp)
|
return sorted(self.query(**criteria), key=lambda x: x.track.timeStamp)
|
||||||
|
|
||||||
|
|
||||||
|
class RelatedTaskWorkItems(AllWorkItems):
|
||||||
|
""" Show work items for all instances of a concept type assigned to
|
||||||
|
the query as query target.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def isQueryTarget(self):
|
||||||
|
return self.conceptManager['querytarget']
|
||||||
|
|
||||||
|
def listWorkItems(self):
|
||||||
|
criteria = self.baseCriteria
|
||||||
|
tasks = []
|
||||||
|
for parent in self.context.getChildren([self.isQueryTarget]):
|
||||||
|
for task in parent.getChildren([self.typePredicate]):
|
||||||
|
tasks.append(util.getUidForObject(task))
|
||||||
|
criteria['task'] = tasks
|
||||||
|
return sorted(self.query(**criteria), key=lambda x: x.track.timeStamp)
|
||||||
|
|
||||||
|
|
||||||
class PersonWorkItems(BaseWorkItemsView, ConceptView):
|
class PersonWorkItems(BaseWorkItemsView, ConceptView):
|
||||||
""" A query view showing work items for a person, the query's parent.
|
""" A query view showing work items for a person, the query's parent.
|
||||||
"""
|
"""
|
||||||
|
@ -343,7 +366,12 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView):
|
||||||
workItems = self.loopsRoot.getRecordManager()[
|
workItems = self.loopsRoot.getRecordManager()[
|
||||||
self.recordManagerName]
|
self.recordManagerName]
|
||||||
return workItems.get(id)
|
return workItems.get(id)
|
||||||
return self.trackFactory(None, 0, None, {})
|
self.task = self.target
|
||||||
|
track = self.trackFactory(None, 0, None, {})
|
||||||
|
types = self.workItemTypes
|
||||||
|
if len(types) == 1:
|
||||||
|
track.workItemType = types[0].name
|
||||||
|
return track
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def title(self):
|
def title(self):
|
||||||
|
@ -379,25 +407,35 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView):
|
||||||
return time.strftime('%Y-%m-%d', time.localtime(ts))
|
return time.strftime('%Y-%m-%d', time.localtime(ts))
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def defaultTimeStamp(self):
|
||||||
|
if self.workItemType.prefillDate:
|
||||||
|
return getTimeStamp()
|
||||||
|
return None
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def date(self):
|
def date(self):
|
||||||
ts = self.track.start or getTimeStamp()
|
ts = self.track.start or self.defaultTimeStamp
|
||||||
|
if ts:
|
||||||
return time.strftime('%Y-%m-%d', time.localtime(ts))
|
return time.strftime('%Y-%m-%d', time.localtime(ts))
|
||||||
|
return ''
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def startTime(self):
|
def startTime(self):
|
||||||
ts = self.track.start or getTimeStamp()
|
ts = self.track.start or self.defaultTimeStamp
|
||||||
#return time.strftime('%Y-%m-%dT%H:%M', time.localtime(ts))
|
if ts:
|
||||||
return time.strftime('T%H:%M', time.localtime(ts))
|
return time.strftime('T%H:%M', time.localtime(ts))
|
||||||
|
return ''
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def endTime(self):
|
def endTime(self):
|
||||||
if self.state == 'running':
|
if self.state == 'running':
|
||||||
ts = getTimeStamp()
|
ts = self.defaultTimeStamp
|
||||||
else:
|
else:
|
||||||
ts = self.track.end or getTimeStamp()
|
ts = self.track.end or self.defaultTimeStamp
|
||||||
#return time.strftime('%Y-%m-%dT%H:%M', time.localtime(ts))
|
if ts:
|
||||||
return time.strftime('T%H:%M', time.localtime(ts))
|
return time.strftime('T%H:%M', time.localtime(ts))
|
||||||
|
return ''
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def state(self):
|
def state(self):
|
||||||
|
|
|
@ -30,6 +30,14 @@
|
||||||
factory="loops.organize.work.browser.AllWorkItems"
|
factory="loops.organize.work.browser.AllWorkItems"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="relatedtaskworkitems.html"
|
||||||
|
for="loops.interfaces.IConcept
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.organize.work.browser.RelatedTaskWorkItems"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
<zope:adapter
|
<zope:adapter
|
||||||
name="taskworkitems.html"
|
name="taskworkitems.html"
|
||||||
for="loops.interfaces.IConcept
|
for="loops.interfaces.IConcept
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -35,7 +35,7 @@ from cybertools.util.jeep import Jeep
|
||||||
from loops.common import adapted, baseObject
|
from loops.common import adapted, baseObject
|
||||||
from loops.expert.browser.report import ReportConceptView
|
from loops.expert.browser.report import ReportConceptView
|
||||||
from loops.expert.field import Field, TargetField, DateField, StateField, \
|
from loops.expert.field import Field, TargetField, DateField, StateField, \
|
||||||
TextField, UrlField
|
TextField, HtmlTextField, UrlField
|
||||||
from loops.expert.field import SubReport, SubReportField
|
from loops.expert.field import SubReport, SubReportField
|
||||||
from loops.expert.report import ReportInstance
|
from loops.expert.report import ReportInstance
|
||||||
from loops import util
|
from loops import util
|
||||||
|
@ -279,7 +279,7 @@ class MeetingMinutesWorkRow(WorkRow):
|
||||||
@Lazy
|
@Lazy
|
||||||
def isActive(self):
|
def isActive(self):
|
||||||
return self.context.state not in (
|
return self.context.state not in (
|
||||||
'finished', 'closed', 'cancelled', 'moved')
|
'finished', 'finished_x', 'closed', 'cancelled', 'moved')
|
||||||
|
|
||||||
|
|
||||||
class MeetingMinutesWork(WorkReportInstance, SubReport):
|
class MeetingMinutesWork(WorkReportInstance, SubReport):
|
||||||
|
@ -329,7 +329,7 @@ taskTitle = UrlField('title', u'Task Title',
|
||||||
description=u'The short description of the task.',
|
description=u'The short description of the task.',
|
||||||
cssClass='header-1',
|
cssClass='header-1',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
taskDescription = TextField('description', u'Description',
|
taskDescription = HtmlTextField('description', u'Description',
|
||||||
description=u'The long description of the task.',
|
description=u'The long description of the task.',
|
||||||
cssClass='header-2',
|
cssClass='header-2',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
|
@ -337,11 +337,11 @@ responsible = TextField('responsible', u'label_responsible',
|
||||||
description=u'Responsible.',
|
description=u'Responsible.',
|
||||||
cssClass='header-2',
|
cssClass='header-2',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
discussion = TextField('discussion', u'label_discussion',
|
discussion = HtmlTextField('discussion', u'label_discussion',
|
||||||
description=u'Discussion.',
|
description=u'Discussion.',
|
||||||
cssClass='header-2',
|
cssClass='header-2',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
consequences = TextField('consequences', u'label_consequences',
|
consequences = HtmlTextField('consequences', u'label_consequences',
|
||||||
description=u'Consequences.',
|
description=u'Consequences.',
|
||||||
cssClass='header-2',
|
cssClass='header-2',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Specialized fields factories.
|
Specialized fields factories.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.component import adapts
|
from zope.component import adapts
|
||||||
|
@ -38,7 +36,7 @@ class ResourceSchemaFactory(SchemaFactory):
|
||||||
def __call__(self, interface, **kw):
|
def __call__(self, interface, **kw):
|
||||||
schema = super(ResourceSchemaFactory, self).__call__(interface, **kw)
|
schema = super(ResourceSchemaFactory, self).__call__(interface, **kw)
|
||||||
#if 'data' in schema.fields.keys():
|
#if 'data' in schema.fields.keys():
|
||||||
schema.fields.data.height = 10
|
schema.fields.data.height = 15
|
||||||
if self.context.contentType == 'text/html':
|
if self.context.contentType == 'text/html':
|
||||||
schema.fields.data.fieldType = 'html'
|
schema.fields.data.fieldType = 'html'
|
||||||
return schema
|
return schema
|
||||||
|
|
|
@ -79,6 +79,11 @@
|
||||||
<grant role="loops.Owner" permission="loops.ViewRestricted" />
|
<grant role="loops.Owner" permission="loops.ViewRestricted" />
|
||||||
<grant role="loops.Owner" permission="zope.View" />
|
<grant role="loops.Owner" permission="zope.View" />
|
||||||
|
|
||||||
|
<role id="loops.Person"
|
||||||
|
title="[loops-person-role] Person" />
|
||||||
|
<grant role="loops.Person" permission="zope.View" />
|
||||||
|
<grant role="loops.Person" permission="loops.ViewRestricted" />
|
||||||
|
|
||||||
<!-- moved to etc/securitypolicy.zcml: -->
|
<!-- moved to etc/securitypolicy.zcml: -->
|
||||||
<!--<grant role="zope.ContentManager" permission="loops.AssignAsParent" />-->
|
<!--<grant role="zope.ContentManager" permission="loops.AssignAsParent" />-->
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Security-related views.
|
Security-related views.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.authentication.groupfolder import GroupInformation
|
from zope.app.authentication.groupfolder import GroupInformation
|
||||||
|
@ -145,7 +143,7 @@ class PermissionView(object):
|
||||||
for e in entry:
|
for e in entry:
|
||||||
value = SettingAsBoolean[e[1]]
|
value = SettingAsBoolean[e[1]]
|
||||||
value = (value is False and '-') or (value and '+') or ''
|
value = (value is False and '-') or (value and '+') or ''
|
||||||
result.append(value + e[0])
|
result.append(value + (e[0] or ''))
|
||||||
return ', '.join(result)
|
return ', '.join(result)
|
||||||
|
|
||||||
def getPrincipalPermissions(self):
|
def getPrincipalPermissions(self):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Common functions and other stuff for working with permissions and roles.
|
Common functions and other stuff for working with permissions and roles.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from persistent import Persistent
|
from persistent import Persistent
|
||||||
|
@ -49,12 +47,12 @@ allRolesExceptOwner = (
|
||||||
'zope.Anonymous', 'zope.Member', 'zope.ContentManager', 'loops.Staff',
|
'zope.Anonymous', 'zope.Member', 'zope.ContentManager', 'loops.Staff',
|
||||||
'loops.xmlrpc.ConceptManager', # relevant for local security?
|
'loops.xmlrpc.ConceptManager', # relevant for local security?
|
||||||
#'loops.SiteManager',
|
#'loops.SiteManager',
|
||||||
'loops.Member', 'loops.Master',)
|
'loops.Person', 'loops.Member', 'loops.Master')
|
||||||
allRolesExceptOwnerAndMaster = tuple(allRolesExceptOwner[:-1])
|
allRolesExceptOwnerAndMaster = tuple(allRolesExceptOwner[:-1])
|
||||||
minorPrivilegedRoles = ('zope.Anonymous', 'zope.Member',)
|
minorPrivilegedRoles = ('zope.Anonymous', 'zope.Member',)
|
||||||
localRoles = ('zope.Anonymous', 'zope.Member', 'zope.ContentManager',
|
localRoles = ('zope.Anonymous', 'zope.Member', 'zope.ContentManager',
|
||||||
'loops.SiteManager', 'loops.Staff', 'loops.Member', 'loops.Master',
|
'loops.SiteManager', 'loops.Staff', 'loops.Member', 'loops.Master',
|
||||||
'loops.Owner')
|
'loops.Owner', 'loops.Person')
|
||||||
|
|
||||||
localPermissions = ('zope.ManageContent', 'zope.View', 'loops.ManageWorkspaces',
|
localPermissions = ('zope.ManageContent', 'zope.View', 'loops.ManageWorkspaces',
|
||||||
'loops.ViewRestricted', 'loops.EditRestricted', 'loops.AssignAsParent',)
|
'loops.ViewRestricted', 'loops.EditRestricted', 'loops.AssignAsParent',)
|
||||||
|
@ -77,7 +75,7 @@ def canListObject(obj, noCheck=False):
|
||||||
return canAccess(obj, 'title')
|
return canAccess(obj, 'title')
|
||||||
|
|
||||||
def canWriteObject(obj):
|
def canWriteObject(obj):
|
||||||
return canWrite(obj, 'title')
|
return canWrite(obj, 'title') or canAssignAsParent(obj)
|
||||||
|
|
||||||
def canEditRestricted(obj):
|
def canEditRestricted(obj):
|
||||||
return checkPermission('loops.EditRestricted', obj)
|
return checkPermission('loops.EditRestricted', obj)
|
||||||
|
@ -122,12 +120,22 @@ def setPrincipalRole(prm, r, p, setting):
|
||||||
|
|
||||||
|
|
||||||
def assignOwner(obj, principalId):
|
def assignOwner(obj, principalId):
|
||||||
prm = IPrincipalRoleManager(obj)
|
prm = IPrincipalRoleManager(obj, None)
|
||||||
|
if prm is not None:
|
||||||
prm.assignRoleToPrincipal('loops.Owner', principalId)
|
prm.assignRoleToPrincipal('loops.Owner', principalId)
|
||||||
|
|
||||||
def removeOwner(obj, principalId):
|
def removeOwner(obj, principalId):
|
||||||
|
prm = IPrincipalRoleManager(obj, None)
|
||||||
|
if prm is not None:
|
||||||
|
prm.unsetRoleForPrincipal('loops.Owner', principalId)
|
||||||
|
|
||||||
|
def assignPersonRole(obj, principalId):
|
||||||
prm = IPrincipalRoleManager(obj)
|
prm = IPrincipalRoleManager(obj)
|
||||||
prm.removeRoleFromPrincipal('loops.Owner', principalId)
|
prm.assignRoleToPrincipal('loops.Person', principalId)
|
||||||
|
|
||||||
|
def removePersonRole(obj, principalId):
|
||||||
|
prm = IPrincipalRoleManager(obj)
|
||||||
|
prm.unsetRoleForPrincipal('loops.Person', principalId)
|
||||||
|
|
||||||
|
|
||||||
def allowEditingForOwner(obj, deny=allRolesExceptOwner, revert=False):
|
def allowEditingForOwner(obj, deny=allRolesExceptOwner, revert=False):
|
||||||
|
@ -161,6 +169,9 @@ def setDefaultSecurity(obj, event):
|
||||||
aObj = adapted(obj)
|
aObj = adapted(obj)
|
||||||
setter = ISecuritySetter(aObj)
|
setter = ISecuritySetter(aObj)
|
||||||
setter.setDefaultSecurity()
|
setter.setDefaultSecurity()
|
||||||
|
principal = getCurrentPrincipal()
|
||||||
|
if principal is not None:
|
||||||
|
assignOwner(obj, principal.id)
|
||||||
|
|
||||||
|
|
||||||
@component.adapter(IConcept, IAssignmentEvent)
|
@component.adapter(IConcept, IAssignmentEvent)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Interfaces for loops security management.
|
Interfaces for loops security management.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import Interface, Attribute
|
from zope.interface import Interface, Attribute
|
||||||
|
@ -35,6 +33,10 @@ class ISecuritySetter(Interface):
|
||||||
context object.
|
context object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def setStateSecurity():
|
||||||
|
""" Set the security according to the state(s) of the object.
|
||||||
|
"""
|
||||||
|
|
||||||
def setDefaultRolePermissions():
|
def setDefaultRolePermissions():
|
||||||
""" Set some default role permission assignments (grants) on the
|
""" Set some default role permission assignments (grants) on the
|
||||||
context object.
|
context object.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,8 +19,6 @@
|
||||||
"""
|
"""
|
||||||
Base classes for security setters, i.e. adapters that provide standardized
|
Base classes for security setters, i.e. adapters that provide standardized
|
||||||
methods for setting role permissions and other security-related stuff.
|
methods for setting role permissions and other security-related stuff.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.security.settings import Allow, Deny, Unset
|
from zope.app.security.settings import Allow, Deny, Unset
|
||||||
|
@ -33,11 +31,14 @@ from zope.securitypolicy.interfaces import \
|
||||||
IRolePermissionMap, IRolePermissionManager, \
|
IRolePermissionMap, IRolePermissionManager, \
|
||||||
IPrincipalRoleMap, IPrincipalRoleManager
|
IPrincipalRoleMap, IPrincipalRoleManager
|
||||||
|
|
||||||
|
from cybertools.meta.interfaces import IOptions
|
||||||
|
from cybertools.stateful.interfaces import IStateful
|
||||||
from loops.common import adapted, AdapterBase, baseObject
|
from loops.common import adapted, AdapterBase, baseObject
|
||||||
|
from loops.config.base import DummyOptions
|
||||||
from loops.interfaces import IConceptSchema, IBaseResourceSchema, ILoopsAdapter
|
from loops.interfaces import IConceptSchema, IBaseResourceSchema, ILoopsAdapter
|
||||||
from loops.organize.util import getPrincipalFolder, getGroupsFolder, getGroupId
|
from loops.organize.util import getPrincipalFolder, getGroupsFolder, getGroupId
|
||||||
from loops.security.common import overrides, setRolePermission, setPrincipalRole
|
from loops.security.common import overrides, setRolePermission, setPrincipalRole
|
||||||
from loops.security.common import acquiringPredicateNames
|
from loops.security.common import allRolesExceptOwner, acquiringPredicateNames
|
||||||
from loops.security.interfaces import ISecuritySetter
|
from loops.security.interfaces import ISecuritySetter
|
||||||
from loops.versioning.interfaces import IVersionable
|
from loops.versioning.interfaces import IVersionable
|
||||||
|
|
||||||
|
@ -58,6 +59,17 @@ class BaseSecuritySetter(object):
|
||||||
def conceptManager(self):
|
def conceptManager(self):
|
||||||
return self.baseObject.getLoopsRoot().getConceptManager()
|
return self.baseObject.getLoopsRoot().getConceptManager()
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def typeOptions(self):
|
||||||
|
type = self.baseObject.getType()
|
||||||
|
if type is None:
|
||||||
|
return DummyOptions()
|
||||||
|
return IOptions(adapted(type), DummyOptions())
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def globalOptions(self):
|
||||||
|
return IOptions(self.baseObject.getLoopsRoot())
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def acquiringPredicates(self):
|
def acquiringPredicates(self):
|
||||||
return [self.conceptManager.get(n) for n in acquiringPredicateNames]
|
return [self.conceptManager.get(n) for n in acquiringPredicateNames]
|
||||||
|
@ -81,6 +93,9 @@ class BaseSecuritySetter(object):
|
||||||
def acquireRolePermissions(self):
|
def acquireRolePermissions(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def acquirePrincipalRoles(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def copyPrincipalRoles(self, source, revert=False):
|
def copyPrincipalRoles(self, source, revert=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -109,6 +124,13 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter):
|
||||||
for p, r, s in rpm.getRolesAndPermissions():
|
for p, r, s in rpm.getRolesAndPermissions():
|
||||||
setRolePermission(rpm, p, r, Unset)
|
setRolePermission(rpm, p, r, Unset)
|
||||||
|
|
||||||
|
def setStateSecurity(self):
|
||||||
|
statesDefs = (self.globalOptions('organize.stateful.concept', []) +
|
||||||
|
(self.typeOptions('organize.stateful') or []))
|
||||||
|
for std in statesDefs:
|
||||||
|
stf = component.getAdapter(self.baseObject, IStateful, name=std)
|
||||||
|
stf.getStateObject().setSecurity(stf)
|
||||||
|
|
||||||
def acquireRolePermissions(self):
|
def acquireRolePermissions(self):
|
||||||
settings = {}
|
settings = {}
|
||||||
for p in self.parents:
|
for p in self.parents:
|
||||||
|
@ -128,15 +150,59 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter):
|
||||||
settings[(p, r)] = s
|
settings[(p, r)] = s
|
||||||
self.setDefaultRolePermissions()
|
self.setDefaultRolePermissions()
|
||||||
self.setRolePermissions(settings)
|
self.setRolePermissions(settings)
|
||||||
|
self.setStateSecurity()
|
||||||
|
|
||||||
def setRolePermissions(self, settings):
|
def setRolePermissions(self, settings):
|
||||||
for (p, r), s in settings.items():
|
for (p, r), s in settings.items():
|
||||||
setRolePermission(self.rolePermissionManager, p, r, s)
|
setRolePermission(self.rolePermissionManager, p, r, s)
|
||||||
|
|
||||||
|
def acquirePrincipalRoles(self):
|
||||||
|
#if baseObject(self.context).workspaceInformation:
|
||||||
|
# return # do not remove/overwrite workspace settings
|
||||||
|
settings = {}
|
||||||
|
for parent in self.parents:
|
||||||
|
if parent == self.baseObject:
|
||||||
|
continue
|
||||||
|
wi = parent.workspaceInformation
|
||||||
|
if wi:
|
||||||
|
if not wi.propagateParentSecurity:
|
||||||
|
continue
|
||||||
|
prm = IPrincipalRoleMap(wi)
|
||||||
|
for r, p, s in prm.getPrincipalsAndRoles():
|
||||||
|
current = settings.get((r, p))
|
||||||
|
if current is None or overrides(s, current):
|
||||||
|
settings[(r, p)] = s
|
||||||
|
prm = IPrincipalRoleMap(parent)
|
||||||
|
for r, p, s in prm.getPrincipalsAndRoles():
|
||||||
|
current = settings.get((r, p))
|
||||||
|
if current is None or overrides(s, current):
|
||||||
|
settings[(r, p)] = s
|
||||||
|
self.setDefaultPrincipalRoles()
|
||||||
|
for setter in self.versionSetters:
|
||||||
|
setter.setPrincipalRoles(settings)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def versionSetters(self):
|
||||||
|
return [self]
|
||||||
|
|
||||||
|
def setDefaultPrincipalRoles(self):
|
||||||
|
prm = self.principalRoleManager
|
||||||
|
# TODO: set loops.Person roles for Person
|
||||||
|
for r, p, s in prm.getPrincipalsAndRoles():
|
||||||
|
if r in allRolesExceptOwner:
|
||||||
|
setPrincipalRole(prm, r, p, Unset)
|
||||||
|
|
||||||
|
def setPrincipalRoles(self, settings):
|
||||||
|
prm = self.principalRoleManager
|
||||||
|
for (r, p), s in settings.items():
|
||||||
|
if r != 'loops.Owner':
|
||||||
|
setPrincipalRole(prm, r, p, s)
|
||||||
|
|
||||||
def copyPrincipalRoles(self, source, revert=False):
|
def copyPrincipalRoles(self, source, revert=False):
|
||||||
prm = IPrincipalRoleMap(baseObject(source.context))
|
prm = IPrincipalRoleMap(baseObject(source.context))
|
||||||
for r, p, s in prm.getPrincipalsAndRoles():
|
for r, p, s in prm.getPrincipalsAndRoles():
|
||||||
if p in self.workspacePrincipals:
|
#if p in self.workspacePrincipals:
|
||||||
|
if r != 'loops.Owner':
|
||||||
if revert:
|
if revert:
|
||||||
setPrincipalRole(self.principalRoleManager, r, p, Unset)
|
setPrincipalRole(self.principalRoleManager, r, p, Unset)
|
||||||
else:
|
else:
|
||||||
|
@ -155,12 +221,13 @@ class ConceptSecuritySetter(LoopsObjectSecuritySetter):
|
||||||
setter = ISecuritySetter(adapted(relation.second))
|
setter = ISecuritySetter(adapted(relation.second))
|
||||||
setter.setDefaultRolePermissions()
|
setter.setDefaultRolePermissions()
|
||||||
setter.acquireRolePermissions()
|
setter.acquireRolePermissions()
|
||||||
wi = baseObject(self.context).workspaceInformation
|
setter.acquirePrincipalRoles()
|
||||||
if wi and not wi.propagateParentSecurity:
|
#wi = baseObject(self.context).workspaceInformation
|
||||||
return
|
#if wi and not wi.propagateParentSecurity:
|
||||||
setter.copyPrincipalRoles(self, revert)
|
# return
|
||||||
if wi:
|
#setter.copyPrincipalRoles(self, revert)
|
||||||
setter.copyPrincipalRoles(ISecuritySetter(wi), revert)
|
#if wi:
|
||||||
|
# setter.copyPrincipalRoles(ISecuritySetter(wi), revert)
|
||||||
setter.propagateSecurity(revert, updated)
|
setter.propagateSecurity(revert, updated)
|
||||||
|
|
||||||
def propagateSecurity(self, revert=False, updated=None):
|
def propagateSecurity(self, revert=False, updated=None):
|
||||||
|
@ -186,6 +253,12 @@ class ResourceSecuritySetter(LoopsObjectSecuritySetter):
|
||||||
def parents(self):
|
def parents(self):
|
||||||
return self.baseObject.getConcepts(self.acquiringPredicates)
|
return self.baseObject.getConcepts(self.acquiringPredicates)
|
||||||
|
|
||||||
|
def setStateSecurity(self):
|
||||||
|
statesDefs = (self.globalOptions('organize.stateful.resource', []))
|
||||||
|
for std in statesDefs:
|
||||||
|
stf = component.getAdapter(self.baseObject, IStateful, name=std)
|
||||||
|
stf.getStateObject().setSecurity(self.context)
|
||||||
|
|
||||||
def setRolePermissions(self, settings):
|
def setRolePermissions(self, settings):
|
||||||
vSetters = [self]
|
vSetters = [self]
|
||||||
vr = IVersionable(baseObject(self.context))
|
vr = IVersionable(baseObject(self.context))
|
||||||
|
@ -204,10 +277,18 @@ class ResourceSecuritySetter(LoopsObjectSecuritySetter):
|
||||||
vSetters = [ISecuritySetter(adapted(v)) for v in versions]
|
vSetters = [ISecuritySetter(adapted(v)) for v in versions]
|
||||||
prm = IPrincipalRoleMap(baseObject(source.context))
|
prm = IPrincipalRoleMap(baseObject(source.context))
|
||||||
for r, p, s in prm.getPrincipalsAndRoles():
|
for r, p, s in prm.getPrincipalsAndRoles():
|
||||||
if p in self.workspacePrincipals:
|
#if p in self.workspacePrincipals:
|
||||||
|
if r != 'loops.Owner' and p in self.workspacePrincipals:
|
||||||
for v in vSetters:
|
for v in vSetters:
|
||||||
if revert:
|
if revert:
|
||||||
setPrincipalRole(v.principalRoleManager, r, p, Unset)
|
setPrincipalRole(v.principalRoleManager, r, p, Unset)
|
||||||
else:
|
else:
|
||||||
setPrincipalRole(v.principalRoleManager, r, p, s)
|
setPrincipalRole(v.principalRoleManager, r, p, s)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def versionSetters(self):
|
||||||
|
vr = IVersionable(baseObject(self.context))
|
||||||
|
versions = list(vr.versions.values())
|
||||||
|
if versions:
|
||||||
|
return [ISecuritySetter(adapted(v)) for v in versions]
|
||||||
|
return [self]
|
||||||
|
|
|
@ -18,7 +18,7 @@ Let's set up a loops site with basic and example concepts and resources.
|
||||||
>>> concepts, resources, views = t.setup()
|
>>> concepts, resources, views = t.setup()
|
||||||
>>> loopsRoot = site['loops']
|
>>> loopsRoot = site['loops']
|
||||||
>>> len(concepts), len(resources), len(views)
|
>>> len(concepts), len(resources), len(views)
|
||||||
(34, 3, 1)
|
(33, 3, 1)
|
||||||
|
|
||||||
>>> from cybertools.tracking.btree import TrackingStorage
|
>>> from cybertools.tracking.btree import TrackingStorage
|
||||||
>>> from loops.system.job import JobRecord
|
>>> from loops.system.job import JobRecord
|
||||||
|
|
|
@ -35,7 +35,7 @@ ZCML setup):
|
||||||
Let's look what setup has provided us with:
|
Let's look what setup has provided us with:
|
||||||
|
|
||||||
>>> len(concepts)
|
>>> len(concepts)
|
||||||
23
|
22
|
||||||
|
|
||||||
Now let's add a few more concepts:
|
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())
|
>>> 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'note', u'person',
|
||||||
u'predicate', u'task', u'textdocument', u'topic', u'training', u'type']
|
u'predicate', u'task', u'textdocument', u'topic', u'type']
|
||||||
>>> sorted(t['name'] for t in xrf.getPredicates())
|
>>> sorted(t['name'] for t in xrf.getPredicates())
|
||||||
[u'depends', u'issubtype', u'knows', u'ownedby', u'provides', u'requires',
|
[u'depends', u'issubtype', u'knows', u'ownedby', u'provides', u'requires',
|
||||||
u'standard']
|
u'standard']
|
||||||
|
@ -96,7 +96,7 @@ All methods that retrieve one object also returns its children and parents:
|
||||||
u'hasType'
|
u'hasType'
|
||||||
>>> sorted(c['name'] for c in ch[0]['objects'])
|
>>> 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'note', u'person',
|
||||||
u'predicate', u'task', u'textdocument', u'topic', u'training', u'type']
|
u'predicate', u'task', u'textdocument', u'topic', u'type']
|
||||||
|
|
||||||
>>> pa = defaultPred['parents']
|
>>> pa = defaultPred['parents']
|
||||||
>>> len(pa)
|
>>> len(pa)
|
||||||
|
@ -115,7 +115,7 @@ We can also retrieve children and parents explicitely:
|
||||||
u'hasType'
|
u'hasType'
|
||||||
>>> sorted(c['name'] for c in ch[0]['objects'])
|
>>> 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'note', u'person',
|
||||||
u'predicate', u'task', u'textdocument', u'topic', u'training', u'type']
|
u'predicate', u'task', u'textdocument', u'topic', u'type']
|
||||||
|
|
||||||
>>> pa = xrf.getParents('5')
|
>>> pa = xrf.getParents('5')
|
||||||
>>> len(pa)
|
>>> len(pa)
|
||||||
|
@ -174,14 +174,14 @@ Updating the concept map
|
||||||
|
|
||||||
>>> topicId = xrf.getObjectByName('topic')['id']
|
>>> topicId = xrf.getObjectByName('topic')['id']
|
||||||
>>> xrf.createConcept(topicId, u'zope2', u'Zope 2')
|
>>> xrf.createConcept(topicId, u'zope2', u'Zope 2')
|
||||||
{'description': u'', 'title': u'Zope 2', 'type': '36', 'id': '75',
|
{'description': u'', 'title': u'Zope 2', 'type': '36', 'id': '72',
|
||||||
'name': u'zope2'}
|
'name': u'zope2'}
|
||||||
|
|
||||||
The name of the concept is checked by a name chooser; if the corresponding
|
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.
|
parameter is empty, the name will be generated from the title.
|
||||||
|
|
||||||
>>> xrf.createConcept(topicId, u'', u'Python')
|
>>> xrf.createConcept(topicId, u'', u'Python')
|
||||||
{'description': u'', 'title': u'Python', 'type': '36', 'id': '77',
|
{'description': u'', 'title': u'Python', 'type': '36', 'id': '74',
|
||||||
'name': u'python'}
|
'name': u'python'}
|
||||||
|
|
||||||
If we try to deassign a ``hasType`` relation nothing will happen; a
|
If we try to deassign a ``hasType`` relation nothing will happen; a
|
||||||
|
|
Loading…
Add table
Reference in a new issue