work in progress: object actions, stateful resources: classification quality
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2538 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
c1e94af299
commit
828dfda5c5
16 changed files with 321 additions and 100 deletions
|
@ -27,7 +27,8 @@ from zope import component
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
|
||||||
from cybertools.browser.action import Action
|
from cybertools.browser.action import Action, actions
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
class TargetAction(Action):
|
class TargetAction(Action):
|
||||||
|
@ -68,7 +69,6 @@ class DialogAction(Action):
|
||||||
if self.fixedType:
|
if self.fixedType:
|
||||||
urlParams['fixed_type'] = 'yes'
|
urlParams['fixed_type'] = 'yes'
|
||||||
urlParams.update(self.addParams)
|
urlParams.update(self.addParams)
|
||||||
#url = self.page.virtualTargetUrlWithSkin
|
|
||||||
url = self.page.virtualTargetUrl
|
url = self.page.virtualTargetUrl
|
||||||
return self.jsOnClick % (self.dialogName, url, self.viewName,
|
return self.jsOnClick % (self.dialogName, url, self.viewName,
|
||||||
urlencode(urlParams))
|
urlencode(urlParams))
|
||||||
|
@ -77,3 +77,20 @@ class DialogAction(Action):
|
||||||
def innerHtmlId(self):
|
def innerHtmlId(self):
|
||||||
return 'dialog.' + self.dialogName
|
return 'dialog.' + self.dialogName
|
||||||
|
|
||||||
|
|
||||||
|
# standard actions
|
||||||
|
|
||||||
|
actions.register('info', 'object', DialogAction,
|
||||||
|
description=_(u'Information about this object.'),
|
||||||
|
viewName='object_info.html',
|
||||||
|
dialogName='object_info',
|
||||||
|
icon='cybertools.icons/info.png',
|
||||||
|
cssClass='icon-action',
|
||||||
|
)
|
||||||
|
|
||||||
|
actions.register('external_edit', 'object', TargetAction,
|
||||||
|
description=_(u'Edit with external editor.'),
|
||||||
|
viewName='external_edit?version=this',
|
||||||
|
icon='edit.gif',
|
||||||
|
cssClass='icon-action',
|
||||||
|
)
|
||||||
|
|
|
@ -50,6 +50,7 @@ from zope.traversing.api import getName, getParent
|
||||||
from cybertools.ajax.dojo import dojoMacroTemplate
|
from cybertools.ajax.dojo import dojoMacroTemplate
|
||||||
from cybertools.browser.view import GenericView
|
from cybertools.browser.view import GenericView
|
||||||
from cybertools.relation.interfaces import IRelationRegistry
|
from cybertools.relation.interfaces import IRelationRegistry
|
||||||
|
from cybertools.stateful.interfaces import IStateful
|
||||||
from cybertools.text import mimetypes
|
from cybertools.text import mimetypes
|
||||||
from cybertools.typology.interfaces import IType, ITypeManager
|
from cybertools.typology.interfaces import IType, ITypeManager
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
|
@ -370,6 +371,21 @@ class BaseView(GenericView, I18NView):
|
||||||
addInfo = u', '.join(e for e in (current, released) if e)
|
addInfo = u', '.join(e for e in (current, released) if e)
|
||||||
return u'%s (%s)' % (versionId, addInfo)
|
return u'%s (%s)' % (versionId, addInfo)
|
||||||
|
|
||||||
|
# states
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def states(self):
|
||||||
|
result = []
|
||||||
|
if not checkPermission('loops.ManageSite', self.context):
|
||||||
|
# TODO: replace by more sensible permission
|
||||||
|
return result
|
||||||
|
target = self.virtualTargetObject
|
||||||
|
for std in ['loops.classification_quality',
|
||||||
|
'loops.simple_publishing']:
|
||||||
|
stf = component.getAdapter(target, IStateful, name=std)
|
||||||
|
result.append(stf)
|
||||||
|
return result
|
||||||
|
|
||||||
# controlling editing
|
# controlling editing
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -431,6 +447,17 @@ class BaseView(GenericView, I18NView):
|
||||||
#cm.register('css', identifier='dojo.css', position=1,
|
#cm.register('css', identifier='dojo.css', position=1,
|
||||||
# resourceName='ajax.dojo/dojo/resources/dojo.css', media='all')
|
# resourceName='ajax.dojo/dojo/resources/dojo.css', media='all')
|
||||||
|
|
||||||
|
def registerDojoDialog(self):
|
||||||
|
self.registerDojo()
|
||||||
|
jsCall = 'dojo.require("dijit.Dialog")'
|
||||||
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
||||||
|
|
||||||
|
def registerDojoTooltipDialog(self):
|
||||||
|
self.registerDojo()
|
||||||
|
jsCall = ('dojo.require("dijit.Dialog");'
|
||||||
|
'dojo.require("dijit.form.Button");')
|
||||||
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
||||||
|
|
||||||
def registerDojoDateWidget(self):
|
def registerDojoDateWidget(self):
|
||||||
self.registerDojo()
|
self.registerDojo()
|
||||||
jsCall = ('dojo.require("dijit.form.DateTextBox"); '
|
jsCall = ('dojo.require("dijit.form.DateTextBox"); '
|
||||||
|
|
|
@ -211,25 +211,6 @@ class ConceptView(BaseView):
|
||||||
def description(self):
|
def description(self):
|
||||||
return self.adapted.description
|
return self.adapted.description
|
||||||
|
|
||||||
def xxx_fieldData(self):
|
|
||||||
# obsolete - use getData() instead
|
|
||||||
# TODO: change view macros accordingly
|
|
||||||
ti = IType(self.context).typeInterface
|
|
||||||
if not ti:
|
|
||||||
return
|
|
||||||
adapter = self.adapted
|
|
||||||
for n, f in schema.getFieldsInOrder(ti):
|
|
||||||
if n in ('title', 'description',): # already shown in header
|
|
||||||
continue
|
|
||||||
value = getattr(adapter, n, '')
|
|
||||||
if not value:
|
|
||||||
continue
|
|
||||||
bound = f.bind(adapter)
|
|
||||||
widget = component.getMultiAdapter(
|
|
||||||
(bound, self.request), IDisplayWidget)
|
|
||||||
widget.setRenderedValue(value)
|
|
||||||
yield dict(title=f.title, value=value, id=n, widget=widget)
|
|
||||||
|
|
||||||
def getData(self, omit=('title', 'description')):
|
def getData(self, omit=('title', 'description')):
|
||||||
data = self.instance.applyTemplate()
|
data = self.instance.applyTemplate()
|
||||||
for k in omit:
|
for k in omit:
|
||||||
|
@ -328,7 +309,9 @@ class ConceptView(BaseView):
|
||||||
def resources(self):
|
def resources(self):
|
||||||
rels = self.context.getResourceRelations()
|
rels = self.context.getResourceRelations()
|
||||||
for r in rels:
|
for r in rels:
|
||||||
yield self.childViewFactory(r, self.request, contextIsSecond=True)
|
#yield self.childViewFactory(r, self.request, contextIsSecond=True)
|
||||||
|
from loops.browser.resource import ResourceRelationView
|
||||||
|
yield ResourceRelationView(r, self.request, contextIsSecond=True)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def view(self):
|
def view(self):
|
||||||
|
|
|
@ -114,6 +114,7 @@
|
||||||
<th i18n:translate="">Size</th>
|
<th i18n:translate="">Size</th>
|
||||||
<th i18n:translate="">Modification Date</th>
|
<th i18n:translate="">Modification Date</th>
|
||||||
<th i18n:translate="">Author(s)</th>
|
<th i18n:translate="">Author(s)</th>
|
||||||
|
<th i18n:translate="">St</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:items repeat="related resources">
|
<tal:items repeat="related resources">
|
||||||
<tal:item define="class python: repeat['related'].odd() and 'even' or 'odd';
|
<tal:item define="class python: repeat['related'].odd() and 'even' or 'odd';
|
||||||
|
@ -141,6 +142,10 @@
|
||||||
</td>
|
</td>
|
||||||
<td><span tal:replace="related/modified">2007-03-30</span></td>
|
<td><span tal:replace="related/modified">2007-03-30</span></td>
|
||||||
<td><span tal:replace="related/creators">John</span></td>
|
<td><span tal:replace="related/creators">John</span></td>
|
||||||
|
<td style="white-space: nowrap"
|
||||||
|
tal:define="target nocall:related">
|
||||||
|
<div metal:use-macro="views/node_macros/object_actions" />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tal:item>
|
</tal:item>
|
||||||
</tal:items>
|
</tal:items>
|
||||||
|
|
|
@ -565,7 +565,14 @@
|
||||||
|
|
||||||
</pages>
|
</pages>
|
||||||
|
|
||||||
<!-- forms (end-user view) -->
|
<!-- dialogs/forms (end-user views) -->
|
||||||
|
|
||||||
|
<page
|
||||||
|
name="object_info.html"
|
||||||
|
for="loops.interfaces.INode"
|
||||||
|
class="loops.browser.node.ObjectInfo"
|
||||||
|
permission="zope.View"
|
||||||
|
/>
|
||||||
|
|
||||||
<page
|
<page
|
||||||
name="create_object.html"
|
name="create_object.html"
|
||||||
|
|
|
@ -44,6 +44,7 @@ from cybertools.composer.interfaces import IInstance
|
||||||
from cybertools.composer.schema.interfaces import ISchemaFactory
|
from cybertools.composer.schema.interfaces import ISchemaFactory
|
||||||
from cybertools.composer.schema.browser.common import schema_macros, schema_edit_macros
|
from cybertools.composer.schema.browser.common import schema_macros, schema_edit_macros
|
||||||
from cybertools.composer.schema.schema import FormState
|
from cybertools.composer.schema.schema import FormState
|
||||||
|
from cybertools.stateful.interfaces import IStateful
|
||||||
from cybertools.typology.interfaces import IType, ITypeManager
|
from cybertools.typology.interfaces import IType, ITypeManager
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
from loops.concept import Concept, ConceptRelation, ResourceRelation
|
from loops.concept import Concept, ConceptRelation, ResourceRelation
|
||||||
|
@ -437,18 +438,31 @@ class EditObject(FormController, I18NView):
|
||||||
formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers)
|
formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers)
|
||||||
self.selected = []
|
self.selected = []
|
||||||
self.old = []
|
self.old = []
|
||||||
|
stateKeys = []
|
||||||
for k in form.keys():
|
for k in form.keys():
|
||||||
if k.startswith(self.prefix):
|
if k.startswith(self.prefix):
|
||||||
fn = k[len(self.prefix):]
|
fn = k[len(self.prefix):]
|
||||||
value = form[k]
|
value = form[k]
|
||||||
if fn.startswith(self.conceptPrefix) and value:
|
if fn.startswith(self.conceptPrefix) and value:
|
||||||
self.collectConcepts(fn[len(self.conceptPrefix):], value)
|
self.collectConcepts(fn[len(self.conceptPrefix):], value)
|
||||||
|
if k.startswith('state.'):
|
||||||
|
stateKeys.append(k)
|
||||||
self.collectAutoConcepts()
|
self.collectAutoConcepts()
|
||||||
if self.old or self.selected:
|
if self.old or self.selected:
|
||||||
self.assignConcepts(obj)
|
self.assignConcepts(obj)
|
||||||
|
for k in stateKeys:
|
||||||
|
self.updateState(k)
|
||||||
notify(ObjectModifiedEvent(obj))
|
notify(ObjectModifiedEvent(obj))
|
||||||
return formState
|
return formState
|
||||||
|
|
||||||
|
def updateState(self, key):
|
||||||
|
trans = self.request.form.get(key, '-')
|
||||||
|
if trans == '-':
|
||||||
|
return
|
||||||
|
stdName = key[len('state.'):]
|
||||||
|
stf = component.getAdapter(self.object, IStateful, name=stdName)
|
||||||
|
stf.doTransition(trans)
|
||||||
|
|
||||||
def handleFileUpload(self, context, value, fieldInstance, formState):
|
def handleFileUpload(self, context, value, fieldInstance, formState):
|
||||||
""" Special handler for fileupload fields;
|
""" Special handler for fileupload fields;
|
||||||
value is a FileUpload instance.
|
value is a FileUpload instance.
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
<tr metal:use-macro="customMacro" />
|
<tr metal:use-macro="customMacro" />
|
||||||
</tal:custom>
|
</tal:custom>
|
||||||
<tr metal:use-macro="view/template/macros/versioning" />
|
<tr metal:use-macro="view/template/macros/versioning" />
|
||||||
|
<tr metal:use-macro="view/template/macros/states" />
|
||||||
<tr metal:use-macro="view/template/macros/buttons" />
|
<tr metal:use-macro="view/template/macros/buttons" />
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -216,6 +217,34 @@
|
||||||
</metal:versioning>
|
</metal:versioning>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:states define-macro="states"
|
||||||
|
tal:define="states view/states"
|
||||||
|
tal:condition="states">
|
||||||
|
<tr><td colspan="5" i18n:translate="" class="headline">States</td></tr>
|
||||||
|
<tr tal:repeat="st states">
|
||||||
|
<tal:state define="stObj st/getStateObject;
|
||||||
|
stDef st/statesDefinition;
|
||||||
|
stTrans st/getAvailableTransitionsForUser">
|
||||||
|
<td colspan="2" i18n:translate=""
|
||||||
|
tal:content="stDef">loops.simple_publishing</td>
|
||||||
|
<td i18n:translate=""
|
||||||
|
tal:content="stObj/title">draft</td>
|
||||||
|
<td>
|
||||||
|
<select name="state.loops.simple_publishing"
|
||||||
|
tal:condition="stTrans"
|
||||||
|
tal:attributes="name string:state.$stDef">
|
||||||
|
<option i18n:translate="" value="-">No change</option>
|
||||||
|
<option tal:repeat="trans stTrans"
|
||||||
|
tal:attributes="value trans/name"
|
||||||
|
tal:content="trans/title">publish</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td />
|
||||||
|
</tal:state>
|
||||||
|
</tr>
|
||||||
|
</metal:states>
|
||||||
|
|
||||||
|
|
||||||
<tr metal:define-macro="buttons" i18n:domain="" class="buttons">
|
<tr metal:define-macro="buttons" i18n:domain="" class="buttons">
|
||||||
<td colspan="5">
|
<td colspan="5">
|
||||||
<input value="Save" type="submit"
|
<input value="Save" type="submit"
|
||||||
|
|
|
@ -175,6 +175,10 @@ div.menu-1, div.menu-2 {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-action {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
.flow-left {
|
.flow-left {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -251,7 +255,6 @@ table.listing th {
|
||||||
padding: 1em 0 1em 0;
|
padding: 1em 0 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* search stuff */
|
/* search stuff */
|
||||||
|
|
||||||
.searchForm input.button, input.submit {
|
.searchForm input.button, input.submit {
|
||||||
|
|
|
@ -383,11 +383,12 @@ class NodeView(BaseView):
|
||||||
return ('%s/.target%s' %
|
return ('%s/.target%s' %
|
||||||
(self.url, util.getUidForObject(target)))
|
(self.url, util.getUidForObject(target)))
|
||||||
|
|
||||||
def getActions(self, category='object'):
|
def getActions(self, category='object', target=None):
|
||||||
actions = []
|
actions = []
|
||||||
self.registerDojo()
|
self.registerDojo()
|
||||||
if category in self.actions:
|
if category in self.actions:
|
||||||
actions.extend(self.actions[category](self))
|
actions.extend(self.actions[category](self))
|
||||||
|
if target is None:
|
||||||
target = self.virtualTarget
|
target = self.virtualTarget
|
||||||
if target is not None:
|
if target is not None:
|
||||||
actions.extend(target.getActions(category, page=self))
|
actions.extend(target.getActions(category, page=self))
|
||||||
|
@ -456,17 +457,23 @@ class NodeView(BaseView):
|
||||||
url = self.virtualTargetUrl
|
url = self.virtualTargetUrl
|
||||||
return ExternalEditorView(target, self.request).load(url=url)
|
return ExternalEditorView(target, self.request).load(url=url)
|
||||||
|
|
||||||
# helper methods
|
|
||||||
|
|
||||||
def registerDojoDialog(self):
|
|
||||||
self.registerDojo()
|
|
||||||
cm = self.controller.macros
|
|
||||||
jsCall = 'dojo.require("dijit.Dialog")'
|
|
||||||
cm.register('js-execute', jsCall, jsCall=jsCall)
|
|
||||||
|
|
||||||
|
|
||||||
# inner HTML views
|
# inner HTML views
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectInfo(NodeView):
|
||||||
|
|
||||||
|
__call__ = innerHtml
|
||||||
|
|
||||||
|
@property
|
||||||
|
def macro(self):
|
||||||
|
return self.template.macros['object_info']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def dialog_name(self):
|
||||||
|
return self.request.get('dialog', 'object_info')
|
||||||
|
|
||||||
|
|
||||||
class InlineEdit(NodeView):
|
class InlineEdit(NodeView):
|
||||||
""" Provides inline editor as inner HTML"""
|
""" Provides inline editor as inner HTML"""
|
||||||
|
|
||||||
|
|
|
@ -25,29 +25,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div tal:define="target nocall:item/targetObjectView"
|
<div tal:define="target nocall:item/targetObjectView"
|
||||||
tal:condition="nocall:target">
|
tal:condition="nocall:target">
|
||||||
<tal:ignore condition="nothing">
|
|
||||||
<div metal:define-macro="editicons"
|
|
||||||
style="float: right;">
|
|
||||||
<span id="xedit_icon"
|
|
||||||
tal:condition="target/xeditable | nothing">
|
|
||||||
<a href="#" title="Edit"
|
|
||||||
tal:attributes="href string:${item/realTargetUrl}/external_edit?version=this;
|
|
||||||
title string:Edit '${target/title}' with External Editor"><img
|
|
||||||
src="edit.gif" alt="Edit"
|
|
||||||
tal:attributes="src context/++resource++edit.gif" /></a>
|
|
||||||
</span>
|
|
||||||
<span id="inlineedit_icon"
|
|
||||||
xtal:condition="item/inlineEditable"
|
|
||||||
tal:condition="nothing">
|
|
||||||
<a href="#" title="Edit" style="padding: 5px"
|
|
||||||
tal:attributes="title string:Edit '${target/title}' with Inline Editor;
|
|
||||||
onclick python: item.inlineEdit(id)"><img
|
|
||||||
src="edit.gif" alt="Edit"
|
|
||||||
tal:attributes="src context/++resource++edit.gif" /></a>
|
|
||||||
</span>
|
|
||||||
<tal:rte define="dummy item/checkRTE" />
|
|
||||||
</div>
|
|
||||||
</tal:ignore>
|
|
||||||
<div class="content-1 subcolumn" id="1.body"
|
<div class="content-1 subcolumn" id="1.body"
|
||||||
tal:define="node nocall:item;
|
tal:define="node nocall:item;
|
||||||
item nocall:target"
|
item nocall:target"
|
||||||
|
@ -208,6 +185,16 @@
|
||||||
</metal:actions>
|
</metal:actions>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:actions define-macro="object_actions">
|
||||||
|
<div style="float: right;"
|
||||||
|
tal:define="target nocall:target|nothing">
|
||||||
|
<tal:actions repeat="action python:view.getActions('object', target=target)">
|
||||||
|
<metal:action use-macro="action/macro" />
|
||||||
|
</tal:actions>
|
||||||
|
</div>
|
||||||
|
</metal:actions>
|
||||||
|
|
||||||
|
|
||||||
<metal:actions define-macro="clipboard">
|
<metal:actions define-macro="clipboard">
|
||||||
<div class="menu-2">loops Development</div>
|
<div class="menu-2">loops Development</div>
|
||||||
</metal:actions>
|
</metal:actions>
|
||||||
|
@ -230,6 +217,11 @@
|
||||||
|
|
||||||
<!-- inner HTML macros -->
|
<!-- inner HTML macros -->
|
||||||
|
|
||||||
|
<metal:info define-macro="object_info">
|
||||||
|
<div>Object Information</div>
|
||||||
|
</metal:info>
|
||||||
|
|
||||||
|
|
||||||
<div metal:define-macro="inline_edit"
|
<div metal:define-macro="inline_edit"
|
||||||
class="content-1" id="1.body">
|
class="content-1" id="1.body">
|
||||||
<form action="." method="post" id="1.form">
|
<form action="." method="post" id="1.form">
|
||||||
|
@ -262,4 +254,3 @@
|
||||||
tal:attributes="src context/++resource++edit.gif" border="0" />
|
tal:attributes="src context/++resource++edit.gif" border="0" />
|
||||||
</a>
|
</a>
|
||||||
</metal:editlink>
|
</metal:editlink>
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ from zope.security.proxy import removeSecurityProxy
|
||||||
from zope.traversing.api import getName, getParent
|
from zope.traversing.api import getName, getParent
|
||||||
from zope.traversing.browser import absoluteURL
|
from zope.traversing.browser import absoluteURL
|
||||||
|
|
||||||
|
from cybertools.browser.action import actions
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from cybertools.xedit.browser import ExternalEditorView, fromUnicode
|
from cybertools.xedit.browser import ExternalEditorView, fromUnicode
|
||||||
from loops.browser.action import DialogAction, TargetAction
|
from loops.browser.action import DialogAction, TargetAction
|
||||||
|
@ -48,6 +49,7 @@ from loops.browser.node import NodeView, node_macros
|
||||||
from loops.common import adapted, NameChooser
|
from loops.common import adapted, NameChooser
|
||||||
from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument
|
from loops.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument
|
||||||
from loops.interfaces import ITypeConcept
|
from loops.interfaces import ITypeConcept
|
||||||
|
from loops.organize.stateful.browser import statefulActions
|
||||||
from loops.versioning.browser import version_macros
|
from loops.versioning.browser import version_macros
|
||||||
from loops.versioning.interfaces import IVersionable
|
from loops.versioning.interfaces import IVersionable
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
@ -182,14 +184,11 @@ class ResourceView(BaseView):
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
def getObjectActions(self, page=None):
|
def getObjectActions(self, page=None):
|
||||||
actions = []
|
acts = ['info']
|
||||||
if page is None:
|
acts.extend('state.' + st for st in statefulActions)
|
||||||
factory, view = Action, self
|
if self.xeditable:
|
||||||
else:
|
acts.append('external_edit')
|
||||||
factory, view = TargetAction, page
|
return actions.get('object', acts, view=self, page=page)
|
||||||
#if self.xeditable:
|
|
||||||
# actions.append(factory(self, page=view,))
|
|
||||||
return actions
|
|
||||||
|
|
||||||
actions = dict(portlet=getPortletActions, object=getObjectActions)
|
actions = dict(portlet=getPortletActions, object=getObjectActions)
|
||||||
|
|
||||||
|
@ -209,6 +208,12 @@ class ResourceView(BaseView):
|
||||||
yield NodeView(node, self.request)
|
yield NodeView(node, self.request)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceRelationView(ResourceView, ConceptRelationView):
|
||||||
|
|
||||||
|
def __init__(self, relation, request, contextIsSecond=False):
|
||||||
|
ConceptRelationView.__init__(self, relation, request, contextIsSecond)
|
||||||
|
|
||||||
|
|
||||||
class ResourceConfigureView(ResourceView, ConceptConfigureView):
|
class ResourceConfigureView(ResourceView, ConceptConfigureView):
|
||||||
|
|
||||||
#def __init__(self, context, request):
|
#def __init__(self, context, request):
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
<div tal:attributes="ondblclick python: item.openEditWindow('configure.html')">
|
<div tal:attributes="ondblclick python: item.openEditWindow('configure.html')">
|
||||||
<tal:body define="itemNum view/itemNum;
|
<tal:body define="itemNum view/itemNum;
|
||||||
id string:$itemNum.body;">
|
id string:$itemNum.body;">
|
||||||
<tal:edit define="target nocall:item;
|
<div metal:use-macro="views/node_macros/object_actions" />
|
||||||
item nocall:node|nocall:view;">
|
|
||||||
<div metal:use-macro="views/node_macros/editicons" />
|
|
||||||
</tal:edit>
|
|
||||||
<h1 tal:content="item/title">Title</h1>
|
<h1 tal:content="item/title">Title</h1>
|
||||||
<p tal:define="description description|item/description"
|
<p tal:define="description description|item/description"
|
||||||
tal:condition="description">
|
tal:condition="description">
|
||||||
|
@ -40,6 +37,7 @@
|
||||||
|
|
||||||
<metal:block define-macro="image">
|
<metal:block define-macro="image">
|
||||||
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
|
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
|
||||||
|
<div metal:use-macro="views/node_macros/object_actions" />
|
||||||
<h3 tal:content="item/title">Title</h3><br />
|
<h3 tal:content="item/title">Title</h3><br />
|
||||||
<img src="#"
|
<img src="#"
|
||||||
tal:attributes="src string:${view/url}/.target${view/targetId}/view" />
|
tal:attributes="src string:${view/url}/.target${view/targetId}/view" />
|
||||||
|
@ -50,6 +48,7 @@
|
||||||
|
|
||||||
<metal:block define-macro="download">
|
<metal:block define-macro="download">
|
||||||
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
|
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
|
||||||
|
<div metal:use-macro="views/node_macros/object_actions" />
|
||||||
<h3 tal:content="item/title">Title</h3>
|
<h3 tal:content="item/title">Title</h3>
|
||||||
<p><i tal:content="item/description">Description</i></p>
|
<p><i tal:content="item/description">Description</i></p>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -64,6 +64,8 @@ not just kept in the adapter.
|
||||||
Controlling classification quality
|
Controlling classification quality
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
We again first have to register states definitions and adapter classes.
|
||||||
|
|
||||||
>>> from loops.organize.stateful.quality import classificationQuality
|
>>> from loops.organize.stateful.quality import classificationQuality
|
||||||
>>> component.provideUtility(classificationQuality(),
|
>>> component.provideUtility(classificationQuality(),
|
||||||
... name='loops.classification_quality')
|
... name='loops.classification_quality')
|
||||||
|
@ -74,10 +76,15 @@ Controlling classification quality
|
||||||
>>> component.provideHandler(assign)
|
>>> component.provideHandler(assign)
|
||||||
>>> component.provideHandler(deassign)
|
>>> component.provideHandler(deassign)
|
||||||
|
|
||||||
|
Now we can get a stateful adapter for a resource.
|
||||||
|
|
||||||
>>> qcheckedDoc01 = component.getAdapter(doc01, IStateful,
|
>>> qcheckedDoc01 = component.getAdapter(doc01, IStateful,
|
||||||
... name='loops.classification_quality')
|
... name='loops.classification_quality')
|
||||||
>>> qcheckedDoc01.state
|
>>> qcheckedDoc01.state
|
||||||
'unclassified'
|
'new'
|
||||||
|
|
||||||
|
Let's create two customer objects to be used for classification of resources
|
||||||
|
later.
|
||||||
|
|
||||||
>>> tCustomer = concepts['customer']
|
>>> tCustomer = concepts['customer']
|
||||||
>>> from loops.concept import Concept
|
>>> from loops.concept import Concept
|
||||||
|
@ -87,6 +94,9 @@ Controlling classification quality
|
||||||
>>> c02 = addAndConfigureObject(concepts, Concept, 'c02', conceptType=tCustomer,
|
>>> c02 = addAndConfigureObject(concepts, Concept, 'c02', conceptType=tCustomer,
|
||||||
... title='DocFive')
|
... title='DocFive')
|
||||||
|
|
||||||
|
When we change the concept assignments of the resource - i.e. its classification
|
||||||
|
- the classification quality state changes automaticalls
|
||||||
|
|
||||||
>>> c01.assignResource(doc01)
|
>>> c01.assignResource(doc01)
|
||||||
>>> qcheckedDoc01 = component.getAdapter(doc01, IStateful,
|
>>> qcheckedDoc01 = component.getAdapter(doc01, IStateful,
|
||||||
... name='loops.classification_quality')
|
... name='loops.classification_quality')
|
||||||
|
@ -97,10 +107,15 @@ Controlling classification quality
|
||||||
>>> qcheckedDoc01.state
|
>>> qcheckedDoc01.state
|
||||||
'classified'
|
'classified'
|
||||||
|
|
||||||
|
In order to mark the classification as "verified" (i.e. quality-checked)
|
||||||
|
we have to perform the corresponding transition explicitly.
|
||||||
|
|
||||||
>>> qcheckedDoc01.doTransition('verify')
|
>>> qcheckedDoc01.doTransition('verify')
|
||||||
>>> qcheckedDoc01.state
|
>>> qcheckedDoc01.state
|
||||||
'verified'
|
'verified'
|
||||||
|
|
||||||
|
Upon later changes of classification the "verified" state gets lost again.
|
||||||
|
|
||||||
>>> c02.deassignResource(doc01)
|
>>> c02.deassignResource(doc01)
|
||||||
>>> qcheckedDoc01.state
|
>>> qcheckedDoc01.state
|
||||||
'classified'
|
'classified'
|
||||||
|
@ -109,6 +124,39 @@ Controlling classification quality
|
||||||
>>> qcheckedDoc01.state
|
>>> qcheckedDoc01.state
|
||||||
'unclassified'
|
'unclassified'
|
||||||
|
|
||||||
|
Changing states when editing
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
We first need a node that provides us access to the resource as its target
|
||||||
|
|
||||||
|
>>> from loops.view import Node
|
||||||
|
>>> node = addAndConfigureObject(views, Node, 'node', target=doc01)
|
||||||
|
|
||||||
|
>>> from loops.browser.form import EditObjectForm, EditObject
|
||||||
|
>>> from zope.publisher.browser import TestRequest
|
||||||
|
|
||||||
|
The form view gives us access to the states of the object.
|
||||||
|
|
||||||
|
>>> form = EditObjectForm(node, TestRequest())
|
||||||
|
>>> for st in form.states:
|
||||||
|
... sto = st.getStateObject()
|
||||||
|
... transitions = st.getAvailableTransitions()
|
||||||
|
... userTrans = st.getAvailableTransitionsForUser()
|
||||||
|
... print st.statesDefinition, sto.title, [t.title for t in transitions],
|
||||||
|
... print [t.title for t in userTrans]
|
||||||
|
loops.classification_quality unclassified ['classify', 'verify'] ['verify']
|
||||||
|
loops.simple_publishing published ['retract'] ['retract']
|
||||||
|
|
||||||
|
Let's now update the form.
|
||||||
|
|
||||||
|
>>> input = {'state.loops.classification_quality': 'verify'}
|
||||||
|
>>> proc = EditObject(form, TestRequest(form=input))
|
||||||
|
>>> proc.update()
|
||||||
|
False
|
||||||
|
|
||||||
|
>>> qcheckedDoc01.state
|
||||||
|
'verified'
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -41,6 +41,9 @@ class StatefulLoopsObject(Stateful, StatefulAdapter):
|
||||||
|
|
||||||
adapts(ILoopsObject)
|
adapts(ILoopsObject)
|
||||||
|
|
||||||
|
def getAvailableTransitionsForUser(self):
|
||||||
|
return self.getAvailableTransitions()
|
||||||
|
|
||||||
|
|
||||||
class SimplePublishable(StatefulLoopsObject):
|
class SimplePublishable(StatefulLoopsObject):
|
||||||
|
|
||||||
|
|
64
organize/stateful/browser.py
Normal file
64
organize/stateful/browser.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Views and actions for states management.
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope import component
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
|
||||||
|
from cybertools.browser.action import Action, actions
|
||||||
|
from cybertools.stateful.interfaces import IStateful
|
||||||
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
statefulActions = ('loops.classification_quality',)
|
||||||
|
|
||||||
|
|
||||||
|
class StateAction(Action):
|
||||||
|
|
||||||
|
url = None
|
||||||
|
definition = None
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def stateful(self):
|
||||||
|
return component.getAdapter(self.view.context, IStateful,
|
||||||
|
name=self.definition)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def description(self):
|
||||||
|
return (u'State information for %s: %s' %
|
||||||
|
(self.definition, self.stateObject.title))
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def stateObject(self):
|
||||||
|
return self.stateful.getStateObject()
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def icon(self):
|
||||||
|
return 'cybertools.icons/led%s.png' % self.stateObject.color
|
||||||
|
|
||||||
|
|
||||||
|
for std in statefulActions:
|
||||||
|
actions.register('state.' + std, 'object', StateAction,
|
||||||
|
definition = std,
|
||||||
|
cssClass='icon-action',
|
||||||
|
)
|
|
@ -39,51 +39,70 @@ from loops.organize.stateful.base import StatefulLoopsObject
|
||||||
@implementer(IStatesDefinition)
|
@implementer(IStatesDefinition)
|
||||||
def classificationQuality():
|
def classificationQuality():
|
||||||
return StatesDefinition('classificationQuality',
|
return StatesDefinition('classificationQuality',
|
||||||
State('unclassified', 'unclassified', ('classify',)),
|
State('new', 'new', ('classify', 'verify',
|
||||||
|
'change_classification', 'remove_classification'),
|
||||||
|
color='red'),
|
||||||
|
State('unclassified', 'unclassified', ('classify', 'verify'),
|
||||||
|
color='red'),
|
||||||
State('classified', 'classified',
|
State('classified', 'classified',
|
||||||
('verify', 'change_classification', 'remove_classification')),
|
('verify', 'change_classification', 'remove_classification'),
|
||||||
|
color='yellow'),
|
||||||
State('verified', 'verified',
|
State('verified', 'verified',
|
||||||
('change_classification', 'remove_classification')),
|
('change_classification', 'remove_classification'),
|
||||||
|
color='green'),
|
||||||
Transition('classify', 'classify', 'classified'),
|
Transition('classify', 'classify', 'classified'),
|
||||||
Transition('verify', 'verify', 'verified'),
|
Transition('verify', 'verify', 'verified'),
|
||||||
Transition('change_classification', 'change classification', 'classified'),
|
Transition('change_classification', 'change classification', 'classified'),
|
||||||
Transition('remove_classification', 'remove classification', 'unclassified'),
|
Transition('remove_classification', 'remove classification', 'unclassified'),
|
||||||
initialState='unclassified')
|
initialState='new')
|
||||||
|
|
||||||
|
|
||||||
class ClassificationQualityCheckable(StatefulLoopsObject):
|
class ClassificationQualityCheckable(StatefulLoopsObject):
|
||||||
|
|
||||||
statesDefinition = 'loops.classification_quality'
|
statesDefinition = 'loops.classification_quality'
|
||||||
|
|
||||||
|
def getAvailableTransitionsForUser(self):
|
||||||
|
return [tr for tr in self.getAvailableTransitions()
|
||||||
|
if tr.name == 'verify']
|
||||||
|
|
||||||
|
# automatic transitions
|
||||||
|
|
||||||
|
def assign(self, relation):
|
||||||
|
if not self.isRelevant(relation):
|
||||||
|
return
|
||||||
|
if self.state in ('new', 'unclassified'):
|
||||||
|
self.doTransition('classify')
|
||||||
|
else:
|
||||||
|
self.doTransition('change_classification')
|
||||||
|
|
||||||
|
def deassign(self, relation):
|
||||||
|
if not self.isRelevant(relation):
|
||||||
|
return
|
||||||
|
if self.state in ('new', 'classified', 'verified'):
|
||||||
|
old = self.context.getParentRelations()
|
||||||
|
if len(old) > 2: # the hasType relation always remains
|
||||||
|
self.doTransition('change_classification')
|
||||||
|
else:
|
||||||
|
self.doTransition('remove_classification')
|
||||||
|
|
||||||
|
def isRelevant(self, relation):
|
||||||
|
""" Return True if the relation given is relevant for changing
|
||||||
|
the quality state.
|
||||||
|
"""
|
||||||
|
return (IResource.providedBy(self.context) and
|
||||||
|
getName(relation.predicate) != 'hasType')
|
||||||
|
|
||||||
|
|
||||||
# event handlers
|
# event handlers
|
||||||
|
|
||||||
@adapter(ILoopsObject, IAssignmentEvent)
|
@adapter(ILoopsObject, IAssignmentEvent)
|
||||||
def assign(obj, event):
|
def assign(obj, event):
|
||||||
target = event.relation.second
|
stf = component.getAdapter(event.relation.second, IStateful,
|
||||||
if not IResource.providedBy(target):
|
name='loops.classification_quality')
|
||||||
return
|
stf.assign(event.relation)
|
||||||
pred = event.relation.predicate
|
|
||||||
if getName(pred) == 'hasType':
|
|
||||||
return
|
|
||||||
stf = component.getAdapter(target, IStateful, name='loops.classification_quality')
|
|
||||||
if stf.state == 'unclassified':
|
|
||||||
stf.doTransition('classify')
|
|
||||||
else:
|
|
||||||
stf.doTransition('change_classification')
|
|
||||||
|
|
||||||
@adapter(ILoopsObject, IDeassignmentEvent)
|
@adapter(ILoopsObject, IDeassignmentEvent)
|
||||||
def deassign(obj, event):
|
def deassign(obj, event):
|
||||||
target = event.relation.second
|
stf = component.getAdapter(event.relation.second, IStateful,
|
||||||
if not IResource.providedBy(target):
|
name='loops.classification_quality')
|
||||||
return
|
stf.deassign(event.relation)
|
||||||
pred = event.relation.predicate
|
|
||||||
if getName(pred) == 'hasType':
|
|
||||||
return
|
|
||||||
stf = component.getAdapter(target, IStateful, name='loops.classification_quality')
|
|
||||||
if stf.state in ('classified', 'verified'):
|
|
||||||
old = target.getParentRelations()
|
|
||||||
if len(old) > 2: # the hasType relation always remains
|
|
||||||
stf.doTransition('change_classification')
|
|
||||||
else:
|
|
||||||
stf.doTransition('remove_classification')
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue