merge branch master

This commit is contained in:
Helmut Merz 2013-03-23 11:32:29 +01:00
commit b5ab23a90b
82 changed files with 2000 additions and 492 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"
extraPlugins="['insertHorizontalRule','createLink', 'viewsource']"
<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>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -0,0 +1 @@
'''package loops.knowledge.qualification'''

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

View 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

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

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

View file

@ -0,0 +1 @@
'''package loops.knowledge.survey'''

124
knowledge/survey/base.py Normal file
View 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
View 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')

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

View 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.
"""

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

View 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">&nbsp;</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 />&nbsp;
</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>

View file

@ -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
importPath = os.path.join(os.path.dirname(__file__), 'data')
def importData(loopsRoot):
importPath = os.path.join(os.path.dirname(__file__), 'data')
baseImportData(loopsRoot, importPath, 'loops_knowledge_de.dmp')
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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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