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
|
||||
===============================================================
|
||||
|
||||
($Id$)
|
||||
|
||||
The loops platform consists up of three basic types of objects:
|
||||
|
||||
(1) concepts: simple interconnected objects usually representing
|
||||
|
@ -612,7 +610,7 @@ Actions
|
|||
>>> view.controller = Controller(view, request)
|
||||
>>> #view.setupController()
|
||||
|
||||
>>> actions = view.getActions('portlet')
|
||||
>>> actions = view.getAllowedActions('portlet')
|
||||
>>> len(actions)
|
||||
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
|
||||
target object's view here:
|
||||
|
||||
>>> actions = view.virtualTarget.getActions('object', page=view)
|
||||
>>> actions = view.virtualTarget.getAllowedActions('object', page=view)
|
||||
>>> #actions[0].url
|
||||
|
||||
'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
|
||||
# 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',
|
||||
dialogName='edit',
|
||||
prerequisites=['registerDojoEditor'],
|
||||
permission='zope.ManageContent',
|
||||
)
|
||||
|
||||
actions.register('edit_concept', 'portlet', DialogAction,
|
||||
|
@ -137,6 +138,7 @@ actions.register('edit_concept', 'portlet', DialogAction,
|
|||
viewName='edit_concept.html',
|
||||
dialogName='edit',
|
||||
prerequisites=['registerDojoEditor'],
|
||||
permission='zope.ManageContent',
|
||||
)
|
||||
|
||||
actions.register('create_concept', 'portlet', DialogAction,
|
||||
|
@ -146,6 +148,7 @@ actions.register('create_concept', 'portlet', DialogAction,
|
|||
dialogName='createConcept',
|
||||
qualifier='create_concept',
|
||||
innerForm='inner_concept_form.html',
|
||||
permission='loops.AssignAsParent',
|
||||
)
|
||||
|
||||
actions.register('create_subtype', 'portlet', DialogAction,
|
||||
|
@ -155,4 +158,5 @@ actions.register('create_subtype', 'portlet', DialogAction,
|
|||
dialogName='createConcept',
|
||||
qualifier='subtype',
|
||||
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
|
||||
# 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 import schema
|
||||
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.proxy import removeSecurityProxy
|
||||
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.browser.view import GenericView
|
||||
from cybertools.meta.interfaces import IOptions
|
||||
from cybertools.meta.element import Element
|
||||
from cybertools.relation.interfaces import IRelationRegistry
|
||||
from cybertools.stateful.interfaces import IStateful
|
||||
from cybertools.text import mimetypes
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
from cybertools.util.date import toLocalTime
|
||||
from cybertools.util.jeep import Jeep
|
||||
from loops.browser.util import normalizeForUrl
|
||||
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.organize.tracking import access
|
||||
from loops.resource import Resource
|
||||
from loops.security.common import checkPermission
|
||||
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
||||
from loops.type import ITypeConcept
|
||||
from loops import util
|
||||
|
@ -131,6 +134,7 @@ class BaseView(GenericView, I18NView):
|
|||
|
||||
actions = {}
|
||||
portlet_actions = []
|
||||
parts = ()
|
||||
icon = None
|
||||
modeName = 'view'
|
||||
isToplevel = False
|
||||
|
@ -184,6 +188,15 @@ class BaseView(GenericView, I18NView):
|
|||
return '%s/.%s-%s' % (baseUrl, targetId, normalizeForUrl(title))
|
||||
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
|
||||
def principalId(self):
|
||||
principal = self.request.principal
|
||||
|
@ -258,6 +271,8 @@ class BaseView(GenericView, I18NView):
|
|||
d = dc.modified or dc.created
|
||||
if isinstance(d, str):
|
||||
d = datetime(*(strptime(d, '%Y-%m-%dT%H:%M')[:6]))
|
||||
else:
|
||||
d = toLocalTime(d)
|
||||
return d
|
||||
|
||||
@Lazy
|
||||
|
@ -486,7 +501,7 @@ class BaseView(GenericView, I18NView):
|
|||
if text is None:
|
||||
return u''
|
||||
htmlPattern = re.compile(r'<(.+)>.+</\1>')
|
||||
if htmlPattern.search(text):
|
||||
if '<br />' in text or htmlPattern.search(text):
|
||||
return text
|
||||
return self.renderText(text, 'text/restructured')
|
||||
|
||||
|
@ -565,16 +580,29 @@ class BaseView(GenericView, I18NView):
|
|||
return DummyOptions()
|
||||
return component.queryAdapter(self.adapted, IOptions) or DummyOptions()
|
||||
|
||||
@Lazy
|
||||
def globalOptions(self):
|
||||
return IOptions(self.loopsRoot)
|
||||
|
||||
@Lazy
|
||||
def typeOptions(self):
|
||||
if self.typeProvider is None:
|
||||
return DummyOptions()
|
||||
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):
|
||||
return IOptions(adapted(relation.predicate), None) or DummyOptions()
|
||||
|
||||
|
@ -648,7 +676,10 @@ class BaseView(GenericView, I18NView):
|
|||
|
||||
# states
|
||||
|
||||
viewStatesPermission = 'zope.ManageContent'
|
||||
@Lazy
|
||||
def viewStatesPermission(self):
|
||||
opt = self.globalOptions('organize.show_states')
|
||||
return opt and opt[0] or 'zope.ManageContent'
|
||||
|
||||
@Lazy
|
||||
def states(self):
|
||||
|
@ -685,6 +716,16 @@ class BaseView(GenericView, I18NView):
|
|||
"""
|
||||
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
|
||||
def showObjectActions(self):
|
||||
return not IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
||||
|
@ -822,6 +863,7 @@ class BaseView(GenericView, I18NView):
|
|||
|
||||
def registerDojoFormAll(self):
|
||||
self.registerDojo()
|
||||
self.registerDojoEditor()
|
||||
cm = self.controller.macros
|
||||
jsCall = ('dojo.require("dijit.form.Form"); '
|
||||
'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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -200,16 +200,22 @@ class BaseRelationView(BaseView):
|
|||
class ConceptView(BaseView):
|
||||
|
||||
template = concept_macros
|
||||
templateName = 'concept.standard'
|
||||
macroName = 'conceptdata'
|
||||
partPrefix = 'part_'
|
||||
defaultParts = ('title', 'fields',
|
||||
'children', 'resources', 'workitems', 'comments',)
|
||||
|
||||
def childViewFactory(self, *args, **kw):
|
||||
return ConceptRelationView(*args, **kw)
|
||||
|
||||
@Lazy
|
||||
def macro(self):
|
||||
return self.template.macros['conceptdata']
|
||||
def macros(self):
|
||||
return self.controller.getTemplateMacros(self.templateName, self.template)
|
||||
|
||||
#def __init__(self, context, request):
|
||||
# super(ConceptView, self).__init__(context, request)
|
||||
@property
|
||||
def macro(self):
|
||||
return self.macros[self.macroName]
|
||||
|
||||
def setupController(self):
|
||||
cont = self.controller
|
||||
|
@ -222,6 +228,24 @@ class ConceptView(BaseView):
|
|||
subMacro=concept_macros.macros['parents'],
|
||||
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
|
||||
def adapted(self):
|
||||
return adapted(self.context, self.languageInfo)
|
||||
|
@ -296,6 +320,10 @@ class ConceptView(BaseView):
|
|||
if r.order != pos:
|
||||
r.order = pos
|
||||
|
||||
@Lazy
|
||||
def filterOptions(self):
|
||||
return self.getOptions('filter.states')
|
||||
|
||||
def getChildren(self, topLevelOnly=True, sort=True, noDuplicates=True,
|
||||
useFilter=True, predicates=None):
|
||||
form = self.request.form
|
||||
|
@ -337,7 +365,7 @@ class ConceptView(BaseView):
|
|||
options = IOptions(adapted(r.predicate), None)
|
||||
if options is not None and options('hide_children'):
|
||||
continue
|
||||
if not fv.check(r.context):
|
||||
if not fv.check(r.context, self.filterOptions):
|
||||
continue
|
||||
yield r
|
||||
|
||||
|
@ -693,3 +721,22 @@ class ListChildren(ConceptView):
|
|||
def macro(self):
|
||||
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">
|
||||
|
||||
|
||||
<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">
|
||||
<div tal:attributes="class string:content-$level;">
|
||||
<metal:block use-macro="view/concept_macros/concepttitle" />
|
||||
|
@ -22,6 +29,20 @@
|
|||
</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_only">
|
||||
<tal:actions condition="view/showObjectActions">
|
||||
|
@ -41,8 +62,10 @@
|
|||
string:$resourceBase/cybertools.icons/table.png" />
|
||||
</a>
|
||||
</h1>
|
||||
<metal:block use-macro="view/concept_macros/filter_input" />
|
||||
</metal:title>
|
||||
<p tal:define="description description|item/renderedDescription"
|
||||
<p metal:define-macro="conceptdescription"
|
||||
tal:define="description description|item/renderedDescription"
|
||||
tal:condition="description">
|
||||
<i tal:content="structure description">Description</i></p>
|
||||
</metal:title>
|
||||
|
|
|
@ -545,6 +545,14 @@
|
|||
factory="loops.browser.concept.ListChildren"
|
||||
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) -->
|
||||
|
||||
<page
|
||||
|
|
|
@ -291,7 +291,8 @@
|
|||
tal:condition="stTrans"
|
||||
tal:attributes="name string:state.$stDef">
|
||||
<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:content="trans/title">publish</option>
|
||||
</select>
|
||||
|
@ -309,7 +310,7 @@
|
|||
<input value="Save" type="submit"
|
||||
i18n:attributes="value"
|
||||
tal:attributes="onClick python: view.closeAction(True) or
|
||||
'return true'">
|
||||
'submit();; return false'">
|
||||
<input type="button" value="Cancel" onClick="dlg.hide();"
|
||||
i18n:attributes="value"
|
||||
tal:condition="view/isInnerHtml"
|
||||
|
@ -322,16 +323,15 @@
|
|||
<!-- overridden field renderers -->
|
||||
|
||||
<metal:html define-macro="input_html">
|
||||
<p>HTML</p>
|
||||
<div name="field" rows="3" style="width: 600px"
|
||||
dojoType="dijit.Editor"
|
||||
<div dojoType="dijit.Editor"
|
||||
extraPlugins="['insertHorizontalRule', 'createLink', 'viewsource']"
|
||||
tal:define="width field/width|nothing;
|
||||
height field/height|python:3"
|
||||
height field/height|python:10"
|
||||
tal:attributes="name name; id name;
|
||||
rows python: height or 3;
|
||||
height python:
|
||||
'%spx' % (height and str(height * 15) or '150');
|
||||
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:">
|
||||
</div>
|
||||
</metal:html>
|
||||
|
|
|
@ -41,18 +41,12 @@ class Base(BaseConceptView):
|
|||
templateName = 'lobo.standard'
|
||||
macroName = None
|
||||
|
||||
@Lazy
|
||||
def macros(self):
|
||||
return self.controller.getTemplateMacros(self.templateName, self.template)
|
||||
|
||||
@property
|
||||
def macro(self):
|
||||
return self.macros[self.macroName]
|
||||
|
||||
@Lazy
|
||||
def params(self):
|
||||
ann = self.request.annotations.get('loops.view', {})
|
||||
return parse_qs(ann.get('params') or '')
|
||||
#@Lazy
|
||||
# better implementation in BaseView:
|
||||
# splits comma-separated list of values automatically
|
||||
#def params(self):
|
||||
# ann = self.request.annotations.get('loops.view', {})
|
||||
# return parse_qs(ann.get('params') or '')
|
||||
|
||||
|
||||
class ConceptView(BaseConceptView):
|
||||
|
@ -152,25 +146,8 @@ class ConceptView(BaseConceptView):
|
|||
class Layout(Base, ConceptView):
|
||||
|
||||
macroName = 'layout'
|
||||
|
||||
def getParts(self):
|
||||
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
|
||||
partPrefix = 'lobo_'
|
||||
defaultParts = ('h1', 'g3',)
|
||||
|
||||
|
||||
class BasePart(Base):
|
||||
|
@ -192,7 +169,7 @@ class BasePart(Base):
|
|||
return preds
|
||||
|
||||
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]
|
||||
result = []
|
||||
childRels = self.context.getChildRelations(self.childPredicates)
|
||||
|
|
|
@ -211,6 +211,22 @@ function closeDialog(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');
|
||||
if (widget != undefined && save) {
|
||||
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
|
||||
# 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.xedit.browser import ExternalEditorView
|
||||
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.i18n.browser import i18n_macros, LanguageInfo
|
||||
from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode
|
||||
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.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
|
||||
|
||||
|
||||
|
@ -150,13 +151,15 @@ class NodeView(BaseView):
|
|||
priority=20)
|
||||
cm.register('portlet_left', 'navigation', title='Navigation',
|
||||
subMacro=node_macros.macros['menu'])
|
||||
if canWrite(self.context, 'title') or (
|
||||
if canWriteObject(self.context) or (
|
||||
# TODO: is this useful in any case?
|
||||
self.virtualTargetObject is not None and
|
||||
canWrite(self.virtualTargetObject, 'title')):
|
||||
canWriteObject(self.virtualTargetObject)):
|
||||
# check if there are any available actions;
|
||||
# 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:
|
||||
cm.register('portlet_right', 'actions', title=_(u'Actions'),
|
||||
subMacro=node_macros.macros['actions'],
|
||||
|
@ -537,7 +540,7 @@ class NodeView(BaseView):
|
|||
return self.makeTargetUrl(self.url, util.getUidForObject(target),
|
||||
target.title)
|
||||
|
||||
def getActions(self, category='object', target=None):
|
||||
def getActions(self, category='object', page=None, target=None):
|
||||
actions = []
|
||||
#self.registerDojo()
|
||||
self.registerDojoFormAll()
|
||||
|
@ -565,9 +568,11 @@ class NodeView(BaseView):
|
|||
description='Open concept map editor in new window',
|
||||
url=cmeUrl, target=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.',
|
||||
page=self, target=target))
|
||||
page=self, target=target,
|
||||
permission='zope.ManageContent'))
|
||||
return actions
|
||||
|
||||
actions = dict(portlet=getPortletActions)
|
||||
|
|
|
@ -293,7 +293,8 @@
|
|||
<metal:actions define-macro="object_actions">
|
||||
<div id="object-actions" class="object-actions"
|
||||
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" />
|
||||
</tal:actions>
|
||||
</div>
|
||||
|
@ -322,7 +323,7 @@
|
|||
<div><a href="logout.html?nextURL=login.html"
|
||||
tal:attributes="href string:logout.html?nextURL=${view/menu/url}"
|
||||
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" />
|
||||
</tal:actions>
|
||||
</metal:actions>
|
||||
|
|
|
@ -113,6 +113,9 @@ class AdapterBase(object):
|
|||
self.context = context
|
||||
self.__parent__ = context # to get the permission stuff right
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.context)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
self.checkAttr(attr)
|
||||
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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
View classes for glossary and glossary items.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
|
||||
|
@ -27,6 +25,7 @@ import itertools
|
|||
from zope import component
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.traversing.api import getName
|
||||
|
||||
from cybertools.browser.action import actions
|
||||
from cybertools.browser.member import IMemberInfoProvider
|
||||
|
@ -53,9 +52,36 @@ actions.register('createBlogPost', 'portlet', DialogAction,
|
|||
fixedType=True,
|
||||
innerForm='inner_concept_form.html',
|
||||
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):
|
||||
creator = data.get('creator')
|
||||
data['creatorId'] = creator
|
||||
|
@ -99,6 +125,8 @@ class BlogView(ConceptView):
|
|||
|
||||
@Lazy
|
||||
def blogOwnerId(self):
|
||||
if getName(self.context.conceptType) != 'blog':
|
||||
return ''
|
||||
pType = self.loopsRoot.getConceptManager()['person']
|
||||
persons = [p for p in self.context.getParents() if p.conceptType == pType]
|
||||
if len(persons) == 1:
|
||||
|
@ -127,6 +155,8 @@ class BlogView(ConceptView):
|
|||
return False
|
||||
|
||||
|
||||
# blog post view
|
||||
|
||||
class BlogPostView(ConceptView):
|
||||
|
||||
@Lazy
|
||||
|
@ -152,6 +182,7 @@ class BlogPostView(ConceptView):
|
|||
description=_(u'Modify blog post.'),
|
||||
viewName='edit_blogpost.html',
|
||||
dialogName='editBlogPost',
|
||||
permission='zope.ManageContent',
|
||||
page=page, target=target))
|
||||
#self.registerDojoTextWidget()
|
||||
self.registerDojoDateWidget()
|
||||
|
@ -170,6 +201,8 @@ class BlogPostView(ConceptView):
|
|||
yield self.childViewFactory(r, self.request, contextIsSecond=True)
|
||||
|
||||
|
||||
# forms and form controllers
|
||||
|
||||
class EditBlogPostForm(EditConceptForm):
|
||||
|
||||
title = _(u'Edit Blog Post')
|
||||
|
|
|
@ -5,6 +5,19 @@
|
|||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
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
|
||||
factory="loops.compound.blog.post.BlogPost"
|
||||
provides="loops.compound.blog.interfaces.IBlogPost"
|
||||
|
@ -24,6 +37,15 @@
|
|||
|
||||
<!-- 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
|
||||
name="blog.html"
|
||||
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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Blogs (weblogs) and blog posts.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
@ -27,9 +25,32 @@ from zope.interface import Interface, Attribute
|
|||
from zope import interface, component, schema
|
||||
|
||||
from loops.compound.interfaces import ICompound
|
||||
from loops.interfaces import HtmlText
|
||||
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):
|
||||
""" 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Blogs and blog posts.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
@ -32,14 +30,31 @@ from zope.traversing.api import getName
|
|||
|
||||
from loops.common import adapted
|
||||
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.security.common import restrictView
|
||||
from loops.setup import addAndConfigureObject
|
||||
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):
|
||||
|
|
|
@ -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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Specialized schema factories
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.component import adapts
|
||||
|
@ -34,6 +32,6 @@ class BlogPostSchemaFactory(SchemaFactory):
|
|||
|
||||
def __call__(self, interface, **kw):
|
||||
schema = super(BlogPostSchemaFactory, self).__call__(interface, **kw)
|
||||
schema.fields.text.height = 10
|
||||
schema.fields.text.height = 15
|
||||
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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Security settings for blogs and blog posts.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
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.security.common import allowEditingForOwner, assignOwner, restrictView
|
||||
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)
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
<!-- ZPT macros for loops.compound.blog views
|
||||
$Id$ -->
|
||||
<html i18n:domain="loops">
|
||||
|
||||
|
||||
<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">
|
||||
<div>
|
||||
|
@ -7,9 +15,12 @@
|
|||
<div tal:repeat="related item/blogPosts"
|
||||
class="blog">
|
||||
<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">
|
||||
<a href="#"
|
||||
tal:content="related/title"
|
||||
<a tal:content="related/title"
|
||||
tal:attributes="href python: view.getUrlForTarget(related);">Post</a>
|
||||
</h1>
|
||||
<div class="info"
|
||||
|
@ -51,7 +62,7 @@
|
|||
<a tal:omit-tag="not:url"
|
||||
tal:content="data/creator"
|
||||
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>
|
||||
</div>
|
||||
|
@ -63,7 +74,7 @@
|
|||
<div class="text"
|
||||
tal:content="structure item/render">Here comes the text...</div>
|
||||
<div class="comment"
|
||||
tal:define="comment data/privateComment"
|
||||
tal:define="comment data/privateComment|nothing"
|
||||
tal:condition="comment">
|
||||
<h4 i18n:translate="" class="headline">Private Comment</h4>
|
||||
<div tal:content="structure python:
|
||||
|
@ -72,3 +83,6 @@
|
|||
<metal:resources use-macro="view/concept_macros/conceptresources" />
|
||||
<metal:block use-macro="view/comment_macros/comments" />
|
||||
</div>
|
||||
|
||||
|
||||
</html>
|
||||
|
|
|
@ -150,7 +150,7 @@ class PageLayout(Base, standard.Layout):
|
|||
|
||||
def getParts(self):
|
||||
parts = ['headline', 'keyquestions', 'quote', 'maintext',
|
||||
'story', 'usecase']
|
||||
'story', 'tip', 'usecase']
|
||||
return self.getPartViews(parts)
|
||||
|
||||
|
||||
|
@ -191,6 +191,11 @@ class Story(PagePart, standard.BasePart):
|
|||
partName = 'story'
|
||||
|
||||
|
||||
class Tip(PagePart, standard.BasePart):
|
||||
|
||||
partName = 'tip'
|
||||
|
||||
|
||||
class UseCase(PagePart, standard.BasePart):
|
||||
|
||||
partName = 'usecase'
|
||||
|
|
|
@ -75,6 +75,14 @@
|
|||
factory="loops.compound.book.browser.Story"
|
||||
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
|
||||
name="lobo_usecase"
|
||||
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'quote', u'Zitat', u'documenttype')
|
||||
concept(u'story', u'Geschichte', u'documenttype')
|
||||
concept(u'tip', u'Tipp', u'documenttype')
|
||||
concept(u'usecase', u'Fallbeispiel', u'documenttype')
|
||||
|
||||
# book structure
|
||||
|
|
|
@ -48,6 +48,7 @@ from loops.base import ParentInfo
|
|||
from loops.common import adapted, AdapterBase
|
||||
from loops.i18n.common import I18NValue
|
||||
from loops.interfaces import IConcept, IConceptRelation, IConceptView
|
||||
from loops.interfaces import IResource
|
||||
from loops.interfaces import IConceptManager, IConceptManagerContained
|
||||
from loops.interfaces import ILoopsContained
|
||||
from loops.interfaces import IIndexAttributes
|
||||
|
@ -199,7 +200,8 @@ class Concept(Contained, Persistent):
|
|||
if sort == 'default':
|
||||
sort = lambda x: (x.order, (x.second.title and x.second.title.lower()))
|
||||
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)
|
||||
|
||||
def getChildren(self, predicates=None, sort='default', noSecurityCheck=False):
|
||||
|
@ -294,7 +296,8 @@ class Concept(Contained, Persistent):
|
|||
if sort == 'default':
|
||||
sort = lambda x: (x.order, x.second.title.lower())
|
||||
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)
|
||||
|
||||
def getResources(self, predicates=None, sort='default', noSecurityCheck=False):
|
||||
|
|
|
@ -27,7 +27,7 @@ configuration):
|
|||
>>> concepts, resources, views = t.setup()
|
||||
|
||||
>>> len(concepts) + len(resources)
|
||||
37
|
||||
36
|
||||
|
||||
>>> loopsRoot = site['loops']
|
||||
|
||||
|
@ -47,11 +47,11 @@ Type- and text-based queries
|
|||
>>> from loops.expert import query
|
||||
>>> qu = query.Title('ty*')
|
||||
>>> list(qu.apply())
|
||||
[0, 2, 68]
|
||||
[0, 2, 65]
|
||||
|
||||
>>> qu = query.Type('loops:*')
|
||||
>>> len(list(qu.apply()))
|
||||
37
|
||||
36
|
||||
|
||||
>>> qu = query.Type('loops:concept:predicate')
|
||||
>>> len(list(qu.apply()))
|
||||
|
|
|
@ -75,4 +75,20 @@
|
|||
class="loops.expert.browser.report.ResultsView"
|
||||
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>
|
||||
|
|
|
@ -165,10 +165,20 @@ class ResultsConceptView(ConceptView):
|
|||
def hasReportPredicate(self):
|
||||
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
|
||||
def report(self):
|
||||
if self.reportName:
|
||||
return adapted(self.conceptManager[self.reportName])
|
||||
reports = self.context.getParents([self.hasReportPredicate])
|
||||
if not reports:
|
||||
type = self.context.conceptType
|
||||
reports = type.getParents([self.hasReportPredicate])
|
||||
return adapted(reports[0])
|
||||
|
|
|
@ -368,7 +368,8 @@
|
|||
i18n:translate=""></td>
|
||||
<td colspan="2">
|
||||
<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">
|
||||
<input type="checkbox"
|
||||
tal:attributes="name string:$name:list;
|
||||
|
|
|
@ -196,8 +196,9 @@ class Search(ConceptView):
|
|||
|
||||
@Lazy
|
||||
def statesDefinitions(self):
|
||||
return [component.getUtility(IStatesDefinition, name=n)
|
||||
for n in self.globalOptions('organize.stateful.resource', ())]
|
||||
stdnames = (self.globalOptions('organize.stateful.resource', []) +
|
||||
self.globalOptions('organize.stateful.special', []))
|
||||
return [component.getUtility(IStatesDefinition, name=n) for n in stdnames]
|
||||
|
||||
@Lazy
|
||||
def selectedStates(self):
|
||||
|
@ -259,6 +260,8 @@ class Search(ConceptView):
|
|||
for std, states in self.selectedStates.items():
|
||||
if std.startswith('state.resource.'):
|
||||
std = std[len('state.resource.'):]
|
||||
elif std.startswith('state.'):
|
||||
std = std[len('state.'):]
|
||||
else:
|
||||
continue
|
||||
stf = component.queryAdapter(obj, IStateful, name=std)
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
set_schema="loops.expert.report.IReportInstance" />
|
||||
</class>
|
||||
|
||||
<adapter factory="loops.expert.standard.TypeInstances"
|
||||
name="type_instances"
|
||||
provides="loops.expert.report.IReportInstance" />
|
||||
|
||||
<utility
|
||||
provides="zope.schema.interfaces.IVocabularyFactory"
|
||||
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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -60,6 +60,11 @@ class TextField(Field):
|
|||
return text
|
||||
|
||||
|
||||
class HtmlTextField(Field):
|
||||
|
||||
format = 'text/html'
|
||||
|
||||
|
||||
class DecimalField(Field):
|
||||
|
||||
format = 'decimal'
|
||||
|
|
|
@ -101,9 +101,11 @@ class ReportInstance(BaseReport):
|
|||
|
||||
def getResults(self, dynaParams=None):
|
||||
crit = self.queryCriteria
|
||||
if crit is None:
|
||||
return []
|
||||
limits = self.limits
|
||||
if crit is None:
|
||||
parts = Jeep()
|
||||
#return ResultSet(self, [])
|
||||
else:
|
||||
if dynaParams is not None:
|
||||
for k, v in dynaParams.items():
|
||||
if k == 'limits':
|
||||
|
|
|
@ -66,13 +66,13 @@ zcml in real life:
|
|||
|
||||
>>> t = searchView.typesForSearch()
|
||||
>>> len(t)
|
||||
16
|
||||
15
|
||||
>>> t.getTermByToken('loops:resource:*').title
|
||||
'Any Resource'
|
||||
|
||||
>>> t = searchView.conceptTypesForSearch()
|
||||
>>> len(t)
|
||||
13
|
||||
12
|
||||
>>> t.getTermByToken('loops:concept:*').title
|
||||
'Any Concept'
|
||||
|
||||
|
@ -91,7 +91,7 @@ a controller attribute for the search view.
|
|||
|
||||
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
|
||||
'submitReplacing("1.results", "1.search.form",
|
||||
"http://127.0.0.1/loops/views/page/.target99/@@searchresults.html");...'
|
||||
"http://127.0.0.1/loops/views/page/.target96/@@searchresults.html");...'
|
||||
|
||||
Basic (text/title) search
|
||||
-------------------------
|
||||
|
@ -177,7 +177,7 @@ of the concepts' titles:
|
|||
>>> request = TestRequest(form=form)
|
||||
>>> view = Search(page, request)
|
||||
>>> view.listConcepts()
|
||||
u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '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
|
||||
------------------------------------
|
||||
|
@ -219,13 +219,13 @@ and thus include the customer type in the preset search types.
|
|||
|
||||
>>> searchView.conceptsForType('loops:concept:customer')
|
||||
[{'token': 'none', 'title': u'not selected'},
|
||||
{'token': '77', 'title': u'Customer 1'},
|
||||
{'token': '79', 'title': u'Customer 2'},
|
||||
{'token': '81', 'title': u'Customer 3'}]
|
||||
{'token': '74', 'title': u'Customer 1'},
|
||||
{'token': '76', 'title': u'Customer 2'},
|
||||
{'token': '78', 'title': u'Customer 3'}]
|
||||
|
||||
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))
|
||||
>>> results = list(resultsView.results)
|
||||
>>> 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()
|
||||
>>> loopsRoot = site['loops']
|
||||
>>> len(concepts), len(resources), len(views)
|
||||
(34, 3, 1)
|
||||
(33, 3, 1)
|
||||
|
||||
|
||||
Importing loops Objects
|
||||
|
@ -44,7 +44,7 @@ Creating the corresponding objects
|
|||
>>> loader = Loader(loopsRoot)
|
||||
>>> loader.load(elements)
|
||||
>>> len(concepts), len(resources), len(views)
|
||||
(35, 3, 1)
|
||||
(34, 3, 1)
|
||||
|
||||
>>> from loops.common import adapted
|
||||
>>> adMyquery = adapted(concepts['myquery'])
|
||||
|
@ -131,7 +131,7 @@ Extracting elements
|
|||
>>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export'))
|
||||
>>> elements = list(extractor.extract())
|
||||
>>> len(elements)
|
||||
68
|
||||
66
|
||||
|
||||
Writing object information to the external storage
|
||||
--------------------------------------------------
|
||||
|
|
|
@ -197,6 +197,8 @@ class DirectoryCollectionProvider(object):
|
|||
extFileType = extFileTypes.get(contentType.split('/')[0] + '/*')
|
||||
if extFileType is None:
|
||||
extFileType = extFileTypes['*/*']
|
||||
if extFileType is None:
|
||||
extFileType = extFileTypes['image/*']
|
||||
if extFileType is None:
|
||||
getLogger('loops.integrator.collection.DirectoryCollectionProvider'
|
||||
).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
|
||||
# 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.size.interfaces import ISized
|
||||
|
||||
from cybertools.composer.schema.interfaces import FieldType
|
||||
from cybertools.relation.interfaces import IDyadicRelation
|
||||
from cybertools.tracking.interfaces import ITrackingStorage
|
||||
from cybertools.util.format import toStr, toUnicode
|
||||
|
@ -37,6 +38,11 @@ from loops import util
|
|||
from loops.util import _
|
||||
|
||||
|
||||
class HtmlText(schema.Text):
|
||||
|
||||
__typeInfo__ = ('html',)
|
||||
|
||||
|
||||
# common interfaces
|
||||
|
||||
class ILoopsObject(Interface):
|
||||
|
|
|
@ -170,28 +170,7 @@ For testing, we first have to provide the needed utilities and settings
|
|||
Competence and Certification Management
|
||||
=======================================
|
||||
|
||||
>>> from cybertools.stateful.interfaces import IStatesDefinition
|
||||
>>> 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
|
||||
>>> tCompetence = concepts['competence']
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
Survey
|
||||
======
|
||||
|
||||
>>> from loops.knowledge.tests import importSurvey
|
||||
>>> importSurvey(loopsRoot)
|
||||
|
||||
>>> from loops.knowledge.survey.browser import SurveyView
|
||||
|
||||
|
||||
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
|
||||
# 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.common import BaseView
|
||||
from loops.browser.concept import ConceptView
|
||||
from loops.expert.browser.report import ResultsConceptView
|
||||
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.util import _
|
||||
|
||||
|
@ -50,6 +47,7 @@ actions.register('createTopic', 'portlet', DialogAction,
|
|||
typeToken='.loops/concepts/topic',
|
||||
fixedType=True,
|
||||
innerForm='inner_concept_form.html',
|
||||
permission='loops.AssignAsParent',
|
||||
)
|
||||
|
||||
actions.register('editTopic', 'portlet', DialogAction,
|
||||
|
@ -57,6 +55,7 @@ actions.register('editTopic', 'portlet', DialogAction,
|
|||
description=_(u'Modify topic.'),
|
||||
viewName='edit_concept.html',
|
||||
dialogName='editTopic',
|
||||
permission='zope.ManageContent',
|
||||
)
|
||||
|
||||
actions.register('createQualification', 'portlet', DialogAction,
|
||||
|
@ -66,6 +65,7 @@ actions.register('createQualification', 'portlet', DialogAction,
|
|||
dialogName='createQualification',
|
||||
prerequisites=['registerDojoDateWidget', 'registerDojoNumberWidget',
|
||||
'registerDojoTextarea'],
|
||||
permission='loops.AssignAsParent',
|
||||
)
|
||||
|
||||
|
||||
|
@ -111,25 +111,3 @@ class Candidates(ConceptView):
|
|||
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
|
||||
xmlns:zope="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
|
@ -66,22 +64,6 @@
|
|||
interface="cybertools.knowledge.interfaces.IKnowledgeProvider" />
|
||||
</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 -->
|
||||
|
||||
<zope:adapter
|
||||
|
@ -100,19 +82,6 @@
|
|||
factory="loops.knowledge.browser.Candidates"
|
||||
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 -->
|
||||
|
||||
<zope:adapter factory="loops.knowledge.schema.PersonSchemaFactory" />
|
||||
|
@ -120,5 +89,7 @@
|
|||
<!-- sub-packages -->
|
||||
|
||||
<include package=".glossary" />
|
||||
<include package=".qualification" />
|
||||
<include package=".survey" />
|
||||
|
||||
</configure>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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'',
|
||||
typeInterface=u'loops.knowledge.interfaces.IPerson',
|
||||
options=u'action.portlet:createQualification,editPerson')
|
||||
|
@ -9,9 +10,9 @@ type(u'task', u'Aufgabe', viewName=u'',
|
|||
type(u'topic', u'Thema', viewName=u'',
|
||||
typeInterface=u'loops.knowledge.interfaces.ITopic',
|
||||
options=u'action.portlet:createTask,createTopic,editTopic')
|
||||
type(u'training', u'Schulung', viewName=u'',
|
||||
typeInterface=u'loops.organize.interfaces.ITask',
|
||||
options=u'action.portlet:edit_concept')
|
||||
#type(u'training', u'Schulung', viewName=u'',
|
||||
# typeInterface=u'loops.organize.interfaces.ITask',
|
||||
# options=u'action.portlet:edit_concept')
|
||||
|
||||
concept(u'general', u'Allgemein', 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'task', 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'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(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'',
|
||||
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'',
|
||||
# typeInterface=u'loops.knowledge.interfaces.IPerson',
|
||||
# options=u'action.portlet:editPerson')
|
||||
|
@ -9,9 +10,9 @@ type(u'competence', u'Kompetenz', viewName=u'',
|
|||
# type(u'topic', u'Thema', viewName=u'',
|
||||
# typeInterface=u'loops.knowledge.interfaces.ITopic',
|
||||
# options=u'action.portlet:createTask,createTopic,editTopic')
|
||||
type(u'training', u'Schulung', viewName=u'',
|
||||
typeInterface=u'loops.organize.interfaces.ITask',
|
||||
options=u'action.portlet:edit_concept')
|
||||
#type(u'training', u'Schulung', viewName=u'',
|
||||
# typeInterface=u'loops.organize.interfaces.ITask',
|
||||
# options=u'action.portlet:edit_concept')
|
||||
|
||||
concept(u'general', u'Allgemein', 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'task', 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'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(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
|
||||
# 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 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 IKnowledgeElement
|
||||
from cybertools.organize.interfaces import IWorkItem, IWorkItems
|
||||
from loops.interfaces import IConceptSchema, ILoopsAdapter
|
||||
from loops.organize.interfaces import IPerson as IBasePerson
|
||||
from loops.organize.interfaces import ITask as IBaseTask
|
||||
from loops.schema.base import Relation, RelationSet
|
||||
|
||||
_ = MessageFactory('loops')
|
||||
from loops.util import _
|
||||
|
||||
|
||||
class IPerson(IBasePerson, IKnowing):
|
||||
|
@ -62,13 +58,3 @@ class ITopic(IConceptSchema, IKnowledgeElement, ILoopsAdapter):
|
|||
""" 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 unittest, doctest
|
||||
from zope.testing.doctestunit import DocFileSuite
|
||||
from zope.app.testing import ztapi
|
||||
from zope import component
|
||||
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.setup import importData as baseImportData
|
||||
|
||||
|
||||
def importData(loopsRoot):
|
||||
importPath = os.path.join(os.path.dirname(__file__), 'data')
|
||||
baseImportData(loopsRoot, importPath, 'loops_knowledge_de.dmp')
|
||||
|
||||
|
||||
def importData(loopsRoot):
|
||||
baseImportData(loopsRoot, importPath, '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):
|
||||
|
|
Binary file not shown.
|
@ -1,9 +1,9 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
"Project-Id-Version: $Id$\n"
|
||||
"Project-Id-Version: 0.13.0\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"
|
||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -170,6 +170,105 @@ msgstr "Glossareintrag anlegen..."
|
|||
msgid "Create Glossary Item"
|
||||
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..."
|
||||
msgstr "Person anlegen..."
|
||||
|
||||
|
@ -212,6 +311,8 @@ msgstr "Adresse bearbeiten..."
|
|||
msgid "Modify address."
|
||||
msgstr "Adresse bearbeiten."
|
||||
|
||||
# general
|
||||
|
||||
msgid "Create Concept, Type = "
|
||||
msgstr "Begriff anlegen, Typ = "
|
||||
|
||||
|
@ -230,6 +331,8 @@ msgstr "Diese Ressource bearbeiten."
|
|||
msgid "Edit Concept"
|
||||
msgstr "Begriff bearbeiten"
|
||||
|
||||
# events and tasks
|
||||
|
||||
msgid "Create Event..."
|
||||
msgstr "Termin anlegen..."
|
||||
|
||||
|
@ -443,9 +546,15 @@ msgstr "Überschrift, sprechende Bezeichnung des Begriffs"
|
|||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
msgid "label_description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
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"
|
||||
|
||||
msgid "desc_description"
|
||||
msgstr "Eine ausführlichere Beschreibung des Inhalts und Zwecks des Objekts"
|
||||
|
||||
msgid "Related Items"
|
||||
msgstr "Verwandte Begriffe"
|
||||
|
||||
|
@ -707,9 +816,6 @@ msgstr "Deckungsgrad"
|
|||
msgid "Create loops Note"
|
||||
msgstr "loops-Notiz anlegen"
|
||||
|
||||
msgid "State information for $definition: $title"
|
||||
msgstr "Status ($definition): $title"
|
||||
|
||||
msgid "User ID"
|
||||
msgstr "Benutzerkennung"
|
||||
|
||||
|
@ -886,65 +992,178 @@ msgstr "an"
|
|||
msgid "move_to_task"
|
||||
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"
|
||||
msgstr "neu"
|
||||
|
||||
msgid "planned"
|
||||
msgstr "geplant"
|
||||
|
||||
msgid "accepted"
|
||||
msgstr "angenommen"
|
||||
msgid "private"
|
||||
msgstr "privat"
|
||||
|
||||
msgid "delegated"
|
||||
msgstr "delegiert"
|
||||
msgid "published"
|
||||
msgstr "veröffentlicht"
|
||||
|
||||
msgid "running"
|
||||
msgstr "in Arbeit"
|
||||
|
||||
msgid "done"
|
||||
msgstr "bearbeitet"
|
||||
|
||||
msgid "finished"
|
||||
msgstr "beendet"
|
||||
|
||||
msgid "closed"
|
||||
msgstr "abgeschlossen"
|
||||
|
||||
msgid "cancelled"
|
||||
msgstr "abgebrochen"
|
||||
|
||||
msgid "moved"
|
||||
msgstr "verschoben"
|
||||
msgid "removed"
|
||||
msgstr "gelöscht"
|
||||
|
||||
msgid "replaced"
|
||||
msgstr "ersetzt"
|
||||
|
||||
msgid "plan"
|
||||
msgstr "planen"
|
||||
msgid "running"
|
||||
msgstr "in Arbeit"
|
||||
|
||||
msgid "unclassified"
|
||||
msgstr "unklassifiziert"
|
||||
|
||||
msgid "verified"
|
||||
msgstr "verifiziert"
|
||||
|
||||
# transitions
|
||||
|
||||
msgid "accept"
|
||||
msgstr "annehmen"
|
||||
|
||||
msgid "start working"
|
||||
msgstr "Arbeit beginnen"
|
||||
msgid "archive"
|
||||
msgstr "archivieren"
|
||||
|
||||
msgid "work"
|
||||
msgstr "bearbeiten"
|
||||
msgid "classify"
|
||||
msgstr "klassifizieren"
|
||||
|
||||
msgid "finish"
|
||||
msgstr "beenden"
|
||||
msgid "change_classification"
|
||||
msgstr "Klassifizierung ändern"
|
||||
|
||||
msgid "cancel"
|
||||
msgstr "abbrechen"
|
||||
|
||||
msgid "close"
|
||||
msgstr "abschließen"
|
||||
|
||||
msgid "delegate"
|
||||
msgstr "delegieren"
|
||||
|
||||
msgid "finish"
|
||||
msgstr "beenden"
|
||||
|
||||
msgid "finish (published)"
|
||||
msgstr "beenden (zugänglich)"
|
||||
|
||||
msgid "hide"
|
||||
msgstr "verstecken"
|
||||
|
||||
msgid "move"
|
||||
msgstr "verschieben"
|
||||
|
||||
msgid "close"
|
||||
msgstr "abschließen"
|
||||
msgid "No change"
|
||||
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"
|
||||
msgstr "Montag"
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
loops - Linked Objects for Organization and Processing Services
|
||||
===============================================================
|
||||
|
||||
($Id$)
|
||||
|
||||
Note: This packages depends on cybertools.organize.
|
||||
|
||||
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
|
||||
>>> IPrincipalRoleMap(concepts['john']).getPrincipalsAndRoles()
|
||||
[('loops.Owner', 'users.john', PermissionSetting: Allow)]
|
||||
[('loops.Person', 'users.john', PermissionSetting: Allow)]
|
||||
>>> 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
|
||||
to it.
|
||||
|
@ -307,9 +305,12 @@ Now we are ready to look for the real stuff - what John is allowed to do.
|
|||
True
|
||||
|
||||
Person objects that have an owner may be modified by this owner.
|
||||
(Changed in 2013-01-14: Owner not set automatically)
|
||||
|
||||
>>> canWrite(john, 'title')
|
||||
True
|
||||
False
|
||||
|
||||
was: True
|
||||
|
||||
So let's try with another user with another role setting.
|
||||
|
||||
|
@ -409,7 +410,7 @@ Send Email to Members
|
|||
>>> form.subject
|
||||
u"loops Notification from '$site'"
|
||||
>>> form.mailBody
|
||||
u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.116\n\n'
|
||||
u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.113\n\n'
|
||||
|
||||
|
||||
Show Presence of Other Users
|
||||
|
|
|
@ -56,6 +56,7 @@ actions.register('createEvent', 'portlet', DialogAction,
|
|||
typeToken='.loops/concepts/event',
|
||||
fixedType=True,
|
||||
prerequisites=['registerDojoDateWidget'],
|
||||
permission='loops.AssignAsParent',
|
||||
)
|
||||
|
||||
actions.register('editEvent', 'portlet', DialogAction,
|
||||
|
@ -383,8 +384,9 @@ class CreateFollowUpEvent(CreateConcept, BaseFollowUpController):
|
|||
bevt = baseObject(self.baseEvent)
|
||||
bevt.assignChild(obj, self.followsPredicate)
|
||||
for rel in bevt.getParentRelations():
|
||||
if rel.predicate != self.view.typePredicate:
|
||||
obj.assignParent(rel.first, rel.predicate)
|
||||
if rel.predicate not in (self.view.typePredicate, self.followsPredicate):
|
||||
obj.assignParent(rel.first, rel.predicate,
|
||||
order=rel.order, relevance=rel.relevance)
|
||||
|
||||
|
||||
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
|
||||
# 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 ITask
|
||||
from loops.interfaces import ILoopsAdapter, IConceptSchema, IRelationAdapter
|
||||
from loops.interfaces import HtmlText
|
||||
from loops.organize.util import getPrincipalFolder
|
||||
from loops import util
|
||||
from loops.util import _
|
||||
|
@ -173,26 +174,36 @@ class IEvent(ITask):
|
|||
|
||||
class IAgendaItem(ILoopsAdapter):
|
||||
|
||||
description = HtmlText(
|
||||
title=_(u'label_description'),
|
||||
description=_(u'desc_description'),
|
||||
default=u'',
|
||||
missing_value=u'',
|
||||
required=False)
|
||||
|
||||
responsible = schema.TextLine(
|
||||
title=_(u'label_responsible'),
|
||||
description=_(u'desc_responsible.'),
|
||||
description=_(u'desc_responsible'),
|
||||
default=u'',
|
||||
required=False)
|
||||
|
||||
discussion = schema.Text(
|
||||
discussion = HtmlText(
|
||||
title=_(u'label_discussion'),
|
||||
description=_(u'desc_discussion.'),
|
||||
description=_(u'desc_discussion'),
|
||||
default=u'',
|
||||
missing_value=u'',
|
||||
required=False)
|
||||
|
||||
consequences = schema.Text(
|
||||
consequences = HtmlText(
|
||||
title=_(u'label_consequences'),
|
||||
description=_(u'desc_consequences.'),
|
||||
description=_(u'desc_consequences'),
|
||||
default=u'',
|
||||
missing_value=u'',
|
||||
required=False)
|
||||
|
||||
description.height = 10
|
||||
discussion.height = consequences.height = 7
|
||||
|
||||
|
||||
# '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
|
||||
# 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 ANNOTATION_KEY
|
||||
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.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
|
||||
|
||||
|
||||
|
@ -82,6 +84,7 @@ class Person(AdapterBase, BasePerson):
|
|||
def getUserId(self):
|
||||
return getattr(self.context, '_userId', None)
|
||||
def setUserId(self, userId):
|
||||
setter = ISecuritySetter(self)
|
||||
if userId:
|
||||
principal = self.getPrincipalForUserId(userId)
|
||||
if principal is None:
|
||||
|
@ -97,13 +100,16 @@ class Person(AdapterBase, BasePerson):
|
|||
if ann is None: # or not isinstance(ann, PersistentMapping):
|
||||
ann = pa[ANNOTATION_KEY] = PersistentMapping()
|
||||
ann[loopsId] = self.context
|
||||
assignOwner(self.context, userId)
|
||||
#assignOwner(self.context, userId)
|
||||
assignPersonRole(self.context, userId)
|
||||
oldUserId = self.userId
|
||||
if oldUserId and oldUserId != userId:
|
||||
self.removeReferenceFromPrincipal(oldUserId)
|
||||
removeOwner(self.context, oldUserId)
|
||||
removePersonRole(self.context, oldUserId)
|
||||
self.context._userId = userId
|
||||
allowEditingForOwner(self.context, revert=not userId)
|
||||
setter.propagateSecurity()
|
||||
allowEditingForOwner(self.context, revert=not userId) # why this?
|
||||
userId = property(getUserId, setUserId)
|
||||
|
||||
def removeReferenceFromPrincipal(self, userId):
|
||||
|
|
|
@ -27,6 +27,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile
|
|||
from zope.cachedescriptors.property import Lazy
|
||||
|
||||
from cybertools.browser.configurator import ViewConfigurator, MacroViewProperty
|
||||
from cybertools.stateful.interfaces import IStateful
|
||||
from loops.browser.node import NodeView
|
||||
from loops.concept import Concept
|
||||
from loops.organize.party import getPersonForUser
|
||||
|
@ -107,7 +108,30 @@ class FilterView(NodeView):
|
|||
result.setdefault(obj.getType(), set([])).add(obj)
|
||||
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
|
||||
if not fs:
|
||||
return True
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
loops - Linked Objects for Organization and Processing Services
|
||||
===============================================================
|
||||
|
||||
($Id$)
|
||||
|
||||
>>> from zope import component
|
||||
>>> 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
|
||||
=============
|
||||
|
||||
|
|
|
@ -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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,13 +18,12 @@
|
|||
|
||||
"""
|
||||
Views and actions for states management.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.i18n import translate
|
||||
|
||||
from cybertools.browser.action import Action, actions
|
||||
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
||||
|
@ -36,9 +35,12 @@ from loops.security.common import checkPermission
|
|||
from loops.util import _
|
||||
|
||||
|
||||
template = ViewPageTemplateFile('view_macros.pt')
|
||||
|
||||
statefulActions = ('classification_quality',
|
||||
'simple_publishing',
|
||||
'task_states',)
|
||||
'task_states',
|
||||
'publishable_task',)
|
||||
|
||||
|
||||
class StateAction(Action):
|
||||
|
@ -53,9 +55,11 @@ class StateAction(Action):
|
|||
|
||||
@Lazy
|
||||
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',
|
||||
mapping=dict(definition=self.definition,
|
||||
title=self.stateObject.title))
|
||||
mapping=dict(definition=definition, title=title))
|
||||
|
||||
@Lazy
|
||||
def stateObject(self):
|
||||
|
@ -77,7 +81,7 @@ for std in statefulActions:
|
|||
#class StateQuery(ConceptView):
|
||||
class StateQuery(BaseView):
|
||||
|
||||
template = ViewPageTemplateFile('view_macros.pt')
|
||||
template = template
|
||||
|
||||
form_action = 'execute_search_action'
|
||||
|
||||
|
@ -144,3 +148,15 @@ class StateQuery(BaseView):
|
|||
uids = q.apply()
|
||||
return self.viewIterator(getObjects(uids, self.loopsRoot))
|
||||
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
|
||||
factory="loops.organize.stateful.base.SimplePublishable"
|
||||
name="simple_publishing" trusted="True" />
|
||||
name="simple_publishing" />
|
||||
<zope:class class="loops.organize.stateful.base.SimplePublishable">
|
||||
<require permission="zope.View"
|
||||
interface="cybertools.stateful.interfaces.IStateful" />
|
||||
|
@ -41,7 +41,7 @@
|
|||
|
||||
<zope:adapter
|
||||
factory="loops.organize.stateful.task.StatefulTask"
|
||||
name="task_states" trusted="True" />
|
||||
name="task_states" />
|
||||
<zope:class class="loops.organize.stateful.task.StatefulTask">
|
||||
<require permission="zope.View"
|
||||
interface="cybertools.stateful.interfaces.IStateful" />
|
||||
|
@ -49,13 +49,27 @@
|
|||
set_schema="cybertools.stateful.interfaces.IStateful" />
|
||||
</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
|
||||
factory="loops.organize.stateful.quality.classificationQuality"
|
||||
name="classification_quality" />
|
||||
|
||||
<zope:adapter
|
||||
factory="loops.organize.stateful.quality.ClassificationQualityCheckable"
|
||||
name="classification_quality" trusted="True" />
|
||||
name="classification_quality" />
|
||||
<zope:class class="loops.organize.stateful.quality.ClassificationQualityCheckable">
|
||||
<require permission="zope.View"
|
||||
interface="cybertools.stateful.interfaces.IStateful" />
|
||||
|
@ -71,6 +85,12 @@
|
|||
class="loops.organize.stateful.browser.StateQuery"
|
||||
permission="zope.View" />
|
||||
|
||||
<browser:page
|
||||
for="loops.interfaces.IConcept"
|
||||
name="filter_input.allstates"
|
||||
class="loops.organize.stateful.browser.FilterAllStates"
|
||||
permission="zope.View" />
|
||||
|
||||
<!-- event handlers -->
|
||||
|
||||
<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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -20,6 +20,7 @@
|
|||
Basic implementations for stateful objects and adapters.
|
||||
"""
|
||||
|
||||
from zope.app.security.settings import Allow, Deny, Unset
|
||||
from zope import component
|
||||
from zope.component import adapter
|
||||
from zope.interface import implementer
|
||||
|
@ -30,24 +31,107 @@ from cybertools.stateful.definition import State, Transition
|
|||
from cybertools.stateful.interfaces import IStatesDefinition, IStateful
|
||||
from loops.common import adapted
|
||||
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)
|
||||
def taskStates():
|
||||
return StatesDefinition('task_states',
|
||||
State('planned', 'planned', ('finish', 'cancel'),
|
||||
State('draft', 'draft', ('release', 'cancel',),
|
||||
color='blue'),
|
||||
State('active', 'active', ('finish', 'cancel',),
|
||||
color='yellow'),
|
||||
State('finished', 'finished', ('reopen'),
|
||||
State('finished', 'finished', ('reopen', 'archive',),
|
||||
color='green'),
|
||||
State('cancelled', 'cancelled', ('reopen'),
|
||||
State('cancelled', 'cancelled', ('reopen',),
|
||||
color='x'),
|
||||
State('archived', 'archived', ('reopen',),
|
||||
color='grey'),
|
||||
Transition('release', 'release', 'active'),
|
||||
Transition('finish', 'finish', 'finished'),
|
||||
Transition('cancel', 'cancel', 'cancelled'),
|
||||
Transition('reopen', 're-open', 'planned'),
|
||||
initialState='planned')
|
||||
Transition('reopen', 're-open', 'draft'),
|
||||
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):
|
||||
|
||||
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">
|
||||
|
@ -56,3 +66,6 @@
|
|||
</form>
|
||||
</div>
|
||||
</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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Base class(es) for track/record managers.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
@ -46,6 +44,10 @@ class BaseRecordManager(object):
|
|||
def loopsRoot(self):
|
||||
return self.context.getLoopsRoot()
|
||||
|
||||
@Lazy
|
||||
def uid(self):
|
||||
return util.getUidForObject(self.context)
|
||||
|
||||
@Lazy
|
||||
def storage(self):
|
||||
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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,9 +17,7 @@
|
|||
#
|
||||
|
||||
"""
|
||||
View class(es) for change tracks.
|
||||
|
||||
$Id$
|
||||
View classes for tracks.
|
||||
"""
|
||||
|
||||
from zope import component
|
||||
|
|
|
@ -151,6 +151,7 @@ class WorkItemDetails(TrackDetails):
|
|||
addParams=dict(id=self.track.__name__))
|
||||
actions = [info, WorkItemStateAction(self)]
|
||||
if self.isLastInRun and self.allowedToEditWorkItem:
|
||||
#if self.allowedToEditWorkItem:
|
||||
self.view.registerDojoDateWidget()
|
||||
self.view.registerDojoNumberWidget()
|
||||
self.view.registerDojoTextarea()
|
||||
|
@ -167,7 +168,10 @@ class WorkItemDetails(TrackDetails):
|
|||
|
||||
@Lazy
|
||||
def allowedToEditWorkItem(self):
|
||||
# if not canAccessObject(self.object.task):
|
||||
# return False
|
||||
if checkPermission('loops.ManageSite', self.object):
|
||||
# or hasRole('loops.Master', self.object):
|
||||
return True
|
||||
if self.track.data.get('creator') == self.personId:
|
||||
return True
|
||||
|
@ -288,6 +292,25 @@ class TaskWorkItems(BaseWorkItemsView, ConceptView):
|
|||
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):
|
||||
""" 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()[
|
||||
self.recordManagerName]
|
||||
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
|
||||
def title(self):
|
||||
|
@ -379,25 +407,35 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView):
|
|||
return time.strftime('%Y-%m-%d', time.localtime(ts))
|
||||
return ''
|
||||
|
||||
@Lazy
|
||||
def defaultTimeStamp(self):
|
||||
if self.workItemType.prefillDate:
|
||||
return getTimeStamp()
|
||||
return None
|
||||
|
||||
@Lazy
|
||||
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 ''
|
||||
|
||||
@Lazy
|
||||
def startTime(self):
|
||||
ts = self.track.start or getTimeStamp()
|
||||
#return time.strftime('%Y-%m-%dT%H:%M', time.localtime(ts))
|
||||
ts = self.track.start or self.defaultTimeStamp
|
||||
if ts:
|
||||
return time.strftime('T%H:%M', time.localtime(ts))
|
||||
return ''
|
||||
|
||||
@Lazy
|
||||
def endTime(self):
|
||||
if self.state == 'running':
|
||||
ts = getTimeStamp()
|
||||
ts = self.defaultTimeStamp
|
||||
else:
|
||||
ts = self.track.end or getTimeStamp()
|
||||
#return time.strftime('%Y-%m-%dT%H:%M', time.localtime(ts))
|
||||
ts = self.track.end or self.defaultTimeStamp
|
||||
if ts:
|
||||
return time.strftime('T%H:%M', time.localtime(ts))
|
||||
return ''
|
||||
|
||||
@Lazy
|
||||
def state(self):
|
||||
|
|
|
@ -30,6 +30,14 @@
|
|||
factory="loops.organize.work.browser.AllWorkItems"
|
||||
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
|
||||
name="taskworkitems.html"
|
||||
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
|
||||
# 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.expert.browser.report import ReportConceptView
|
||||
from loops.expert.field import Field, TargetField, DateField, StateField, \
|
||||
TextField, UrlField
|
||||
TextField, HtmlTextField, UrlField
|
||||
from loops.expert.field import SubReport, SubReportField
|
||||
from loops.expert.report import ReportInstance
|
||||
from loops import util
|
||||
|
@ -279,7 +279,7 @@ class MeetingMinutesWorkRow(WorkRow):
|
|||
@Lazy
|
||||
def isActive(self):
|
||||
return self.context.state not in (
|
||||
'finished', 'closed', 'cancelled', 'moved')
|
||||
'finished', 'finished_x', 'closed', 'cancelled', 'moved')
|
||||
|
||||
|
||||
class MeetingMinutesWork(WorkReportInstance, SubReport):
|
||||
|
@ -329,7 +329,7 @@ taskTitle = UrlField('title', u'Task Title',
|
|||
description=u'The short description of the task.',
|
||||
cssClass='header-1',
|
||||
executionSteps=['output'])
|
||||
taskDescription = TextField('description', u'Description',
|
||||
taskDescription = HtmlTextField('description', u'Description',
|
||||
description=u'The long description of the task.',
|
||||
cssClass='header-2',
|
||||
executionSteps=['output'])
|
||||
|
@ -337,11 +337,11 @@ responsible = TextField('responsible', u'label_responsible',
|
|||
description=u'Responsible.',
|
||||
cssClass='header-2',
|
||||
executionSteps=['output'])
|
||||
discussion = TextField('discussion', u'label_discussion',
|
||||
discussion = HtmlTextField('discussion', u'label_discussion',
|
||||
description=u'Discussion.',
|
||||
cssClass='header-2',
|
||||
executionSteps=['output'])
|
||||
consequences = TextField('consequences', u'label_consequences',
|
||||
consequences = HtmlTextField('consequences', u'label_consequences',
|
||||
description=u'Consequences.',
|
||||
cssClass='header-2',
|
||||
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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Specialized fields factories.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.component import adapts
|
||||
|
@ -38,7 +36,7 @@ class ResourceSchemaFactory(SchemaFactory):
|
|||
def __call__(self, interface, **kw):
|
||||
schema = super(ResourceSchemaFactory, self).__call__(interface, **kw)
|
||||
#if 'data' in schema.fields.keys():
|
||||
schema.fields.data.height = 10
|
||||
schema.fields.data.height = 15
|
||||
if self.context.contentType == 'text/html':
|
||||
schema.fields.data.fieldType = 'html'
|
||||
return schema
|
||||
|
|
|
@ -79,6 +79,11 @@
|
|||
<grant role="loops.Owner" permission="loops.ViewRestricted" />
|
||||
<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: -->
|
||||
<!--<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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Security-related views.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.app.authentication.groupfolder import GroupInformation
|
||||
|
@ -145,7 +143,7 @@ class PermissionView(object):
|
|||
for e in entry:
|
||||
value = SettingAsBoolean[e[1]]
|
||||
value = (value is False and '-') or (value and '+') or ''
|
||||
result.append(value + e[0])
|
||||
result.append(value + (e[0] or ''))
|
||||
return ', '.join(result)
|
||||
|
||||
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
|
||||
# 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.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from persistent import Persistent
|
||||
|
@ -49,12 +47,12 @@ allRolesExceptOwner = (
|
|||
'zope.Anonymous', 'zope.Member', 'zope.ContentManager', 'loops.Staff',
|
||||
'loops.xmlrpc.ConceptManager', # relevant for local security?
|
||||
#'loops.SiteManager',
|
||||
'loops.Member', 'loops.Master',)
|
||||
'loops.Person', 'loops.Member', 'loops.Master')
|
||||
allRolesExceptOwnerAndMaster = tuple(allRolesExceptOwner[:-1])
|
||||
minorPrivilegedRoles = ('zope.Anonymous', 'zope.Member',)
|
||||
localRoles = ('zope.Anonymous', 'zope.Member', 'zope.ContentManager',
|
||||
'loops.SiteManager', 'loops.Staff', 'loops.Member', 'loops.Master',
|
||||
'loops.Owner')
|
||||
'loops.Owner', 'loops.Person')
|
||||
|
||||
localPermissions = ('zope.ManageContent', 'zope.View', 'loops.ManageWorkspaces',
|
||||
'loops.ViewRestricted', 'loops.EditRestricted', 'loops.AssignAsParent',)
|
||||
|
@ -77,7 +75,7 @@ def canListObject(obj, noCheck=False):
|
|||
return canAccess(obj, 'title')
|
||||
|
||||
def canWriteObject(obj):
|
||||
return canWrite(obj, 'title')
|
||||
return canWrite(obj, 'title') or canAssignAsParent(obj)
|
||||
|
||||
def canEditRestricted(obj):
|
||||
return checkPermission('loops.EditRestricted', obj)
|
||||
|
@ -122,12 +120,22 @@ def setPrincipalRole(prm, r, p, setting):
|
|||
|
||||
|
||||
def assignOwner(obj, principalId):
|
||||
prm = IPrincipalRoleManager(obj)
|
||||
prm = IPrincipalRoleManager(obj, None)
|
||||
if prm is not None:
|
||||
prm.assignRoleToPrincipal('loops.Owner', 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.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):
|
||||
|
@ -161,6 +169,9 @@ def setDefaultSecurity(obj, event):
|
|||
aObj = adapted(obj)
|
||||
setter = ISecuritySetter(aObj)
|
||||
setter.setDefaultSecurity()
|
||||
principal = getCurrentPrincipal()
|
||||
if principal is not None:
|
||||
assignOwner(obj, principal.id)
|
||||
|
||||
|
||||
@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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Interfaces for loops security management.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.interface import Interface, Attribute
|
||||
|
@ -35,6 +33,10 @@ class ISecuritySetter(Interface):
|
|||
context object.
|
||||
"""
|
||||
|
||||
def setStateSecurity():
|
||||
""" Set the security according to the state(s) of the object.
|
||||
"""
|
||||
|
||||
def setDefaultRolePermissions():
|
||||
""" Set some default role permission assignments (grants) on the
|
||||
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
|
||||
# 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
|
||||
methods for setting role permissions and other security-related stuff.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.app.security.settings import Allow, Deny, Unset
|
||||
|
@ -33,11 +31,14 @@ from zope.securitypolicy.interfaces import \
|
|||
IRolePermissionMap, IRolePermissionManager, \
|
||||
IPrincipalRoleMap, IPrincipalRoleManager
|
||||
|
||||
from cybertools.meta.interfaces import IOptions
|
||||
from cybertools.stateful.interfaces import IStateful
|
||||
from loops.common import adapted, AdapterBase, baseObject
|
||||
from loops.config.base import DummyOptions
|
||||
from loops.interfaces import IConceptSchema, IBaseResourceSchema, ILoopsAdapter
|
||||
from loops.organize.util import getPrincipalFolder, getGroupsFolder, getGroupId
|
||||
from loops.security.common import overrides, setRolePermission, setPrincipalRole
|
||||
from loops.security.common import acquiringPredicateNames
|
||||
from loops.security.common import allRolesExceptOwner, acquiringPredicateNames
|
||||
from loops.security.interfaces import ISecuritySetter
|
||||
from loops.versioning.interfaces import IVersionable
|
||||
|
||||
|
@ -58,6 +59,17 @@ class BaseSecuritySetter(object):
|
|||
def conceptManager(self):
|
||||
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
|
||||
def acquiringPredicates(self):
|
||||
return [self.conceptManager.get(n) for n in acquiringPredicateNames]
|
||||
|
@ -81,6 +93,9 @@ class BaseSecuritySetter(object):
|
|||
def acquireRolePermissions(self):
|
||||
pass
|
||||
|
||||
def acquirePrincipalRoles(self):
|
||||
pass
|
||||
|
||||
def copyPrincipalRoles(self, source, revert=False):
|
||||
pass
|
||||
|
||||
|
@ -109,6 +124,13 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter):
|
|||
for p, r, s in rpm.getRolesAndPermissions():
|
||||
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):
|
||||
settings = {}
|
||||
for p in self.parents:
|
||||
|
@ -128,15 +150,59 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter):
|
|||
settings[(p, r)] = s
|
||||
self.setDefaultRolePermissions()
|
||||
self.setRolePermissions(settings)
|
||||
self.setStateSecurity()
|
||||
|
||||
def setRolePermissions(self, settings):
|
||||
for (p, r), s in settings.items():
|
||||
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):
|
||||
prm = IPrincipalRoleMap(baseObject(source.context))
|
||||
for r, p, s in prm.getPrincipalsAndRoles():
|
||||
if p in self.workspacePrincipals:
|
||||
#if p in self.workspacePrincipals:
|
||||
if r != 'loops.Owner':
|
||||
if revert:
|
||||
setPrincipalRole(self.principalRoleManager, r, p, Unset)
|
||||
else:
|
||||
|
@ -155,12 +221,13 @@ class ConceptSecuritySetter(LoopsObjectSecuritySetter):
|
|||
setter = ISecuritySetter(adapted(relation.second))
|
||||
setter.setDefaultRolePermissions()
|
||||
setter.acquireRolePermissions()
|
||||
wi = baseObject(self.context).workspaceInformation
|
||||
if wi and not wi.propagateParentSecurity:
|
||||
return
|
||||
setter.copyPrincipalRoles(self, revert)
|
||||
if wi:
|
||||
setter.copyPrincipalRoles(ISecuritySetter(wi), revert)
|
||||
setter.acquirePrincipalRoles()
|
||||
#wi = baseObject(self.context).workspaceInformation
|
||||
#if wi and not wi.propagateParentSecurity:
|
||||
# return
|
||||
#setter.copyPrincipalRoles(self, revert)
|
||||
#if wi:
|
||||
# setter.copyPrincipalRoles(ISecuritySetter(wi), revert)
|
||||
setter.propagateSecurity(revert, updated)
|
||||
|
||||
def propagateSecurity(self, revert=False, updated=None):
|
||||
|
@ -186,6 +253,12 @@ class ResourceSecuritySetter(LoopsObjectSecuritySetter):
|
|||
def parents(self):
|
||||
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):
|
||||
vSetters = [self]
|
||||
vr = IVersionable(baseObject(self.context))
|
||||
|
@ -204,10 +277,18 @@ class ResourceSecuritySetter(LoopsObjectSecuritySetter):
|
|||
vSetters = [ISecuritySetter(adapted(v)) for v in versions]
|
||||
prm = IPrincipalRoleMap(baseObject(source.context))
|
||||
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:
|
||||
if revert:
|
||||
setPrincipalRole(v.principalRoleManager, r, p, Unset)
|
||||
else:
|
||||
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()
|
||||
>>> loopsRoot = site['loops']
|
||||
>>> len(concepts), len(resources), len(views)
|
||||
(34, 3, 1)
|
||||
(33, 3, 1)
|
||||
|
||||
>>> from cybertools.tracking.btree import TrackingStorage
|
||||
>>> from loops.system.job import JobRecord
|
||||
|
|
|
@ -35,7 +35,7 @@ ZCML setup):
|
|||
Let's look what setup has provided us with:
|
||||
|
||||
>>> len(concepts)
|
||||
23
|
||||
22
|
||||
|
||||
Now let's add a few more concepts:
|
||||
|
||||
|
@ -73,7 +73,7 @@ applied in an explicit assignment.
|
|||
|
||||
>>> sorted(t['name'] for t in xrf.getConceptTypes())
|
||||
[u'competence', u'customer', u'domain', u'file', u'note', u'person',
|
||||
u'predicate', u'task', u'textdocument', u'topic', u'training', u'type']
|
||||
u'predicate', u'task', u'textdocument', u'topic', u'type']
|
||||
>>> sorted(t['name'] for t in xrf.getPredicates())
|
||||
[u'depends', u'issubtype', u'knows', u'ownedby', u'provides', u'requires',
|
||||
u'standard']
|
||||
|
@ -96,7 +96,7 @@ All methods that retrieve one object also returns its children and parents:
|
|||
u'hasType'
|
||||
>>> sorted(c['name'] for c in ch[0]['objects'])
|
||||
[u'competence', u'customer', u'domain', u'file', u'note', u'person',
|
||||
u'predicate', u'task', u'textdocument', u'topic', u'training', u'type']
|
||||
u'predicate', u'task', u'textdocument', u'topic', u'type']
|
||||
|
||||
>>> pa = defaultPred['parents']
|
||||
>>> len(pa)
|
||||
|
@ -115,7 +115,7 @@ We can also retrieve children and parents explicitely:
|
|||
u'hasType'
|
||||
>>> sorted(c['name'] for c in ch[0]['objects'])
|
||||
[u'competence', u'customer', u'domain', u'file', u'note', u'person',
|
||||
u'predicate', u'task', u'textdocument', u'topic', u'training', u'type']
|
||||
u'predicate', u'task', u'textdocument', u'topic', u'type']
|
||||
|
||||
>>> pa = xrf.getParents('5')
|
||||
>>> len(pa)
|
||||
|
@ -174,14 +174,14 @@ Updating the concept map
|
|||
|
||||
>>> topicId = xrf.getObjectByName('topic')['id']
|
||||
>>> xrf.createConcept(topicId, u'zope2', u'Zope 2')
|
||||
{'description': u'', 'title': u'Zope 2', 'type': '36', 'id': '75',
|
||||
{'description': u'', 'title': u'Zope 2', 'type': '36', 'id': '72',
|
||||
'name': u'zope2'}
|
||||
|
||||
The name of the concept is checked by a name chooser; if the corresponding
|
||||
parameter is empty, the name will be generated from the title.
|
||||
|
||||
>>> xrf.createConcept(topicId, u'', u'Python')
|
||||
{'description': u'', 'title': u'Python', 'type': '36', 'id': '77',
|
||||
{'description': u'', 'title': u'Python', 'type': '36', 'id': '74',
|
||||
'name': u'python'}
|
||||
|
||||
If we try to deassign a ``hasType`` relation nothing will happen; a
|
||||
|
|
Loading…
Add table
Reference in a new issue