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.cachedescriptors.property import Lazy
|
||||
|
||||
from cybertools.browser.action import Action
|
||||
from cybertools.browser.action import Action, actions
|
||||
from loops.util import _
|
||||
|
||||
|
||||
class TargetAction(Action):
|
||||
|
@ -68,7 +69,6 @@ class DialogAction(Action):
|
|||
if self.fixedType:
|
||||
urlParams['fixed_type'] = 'yes'
|
||||
urlParams.update(self.addParams)
|
||||
#url = self.page.virtualTargetUrlWithSkin
|
||||
url = self.page.virtualTargetUrl
|
||||
return self.jsOnClick % (self.dialogName, url, self.viewName,
|
||||
urlencode(urlParams))
|
||||
|
@ -77,3 +77,20 @@ class DialogAction(Action):
|
|||
def innerHtmlId(self):
|
||||
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.browser.view import GenericView
|
||||
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 loops.common import adapted
|
||||
|
@ -370,6 +371,21 @@ class BaseView(GenericView, I18NView):
|
|||
addInfo = u', '.join(e for e in (current, released) if e)
|
||||
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
|
||||
|
||||
@Lazy
|
||||
|
@ -431,6 +447,17 @@ class BaseView(GenericView, I18NView):
|
|||
#cm.register('css', identifier='dojo.css', position=1,
|
||||
# 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):
|
||||
self.registerDojo()
|
||||
jsCall = ('dojo.require("dijit.form.DateTextBox"); '
|
||||
|
|
|
@ -211,25 +211,6 @@ class ConceptView(BaseView):
|
|||
def description(self):
|
||||
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')):
|
||||
data = self.instance.applyTemplate()
|
||||
for k in omit:
|
||||
|
@ -328,7 +309,9 @@ class ConceptView(BaseView):
|
|||
def resources(self):
|
||||
rels = self.context.getResourceRelations()
|
||||
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
|
||||
def view(self):
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
<th i18n:translate="">Size</th>
|
||||
<th i18n:translate="">Modification Date</th>
|
||||
<th i18n:translate="">Author(s)</th>
|
||||
<th i18n:translate="">St</th>
|
||||
</tr>
|
||||
<tal:items repeat="related resources">
|
||||
<tal:item define="class python: repeat['related'].odd() and 'even' or 'odd';
|
||||
|
@ -141,6 +142,10 @@
|
|||
</td>
|
||||
<td><span tal:replace="related/modified">2007-03-30</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>
|
||||
</tal:item>
|
||||
</tal:items>
|
||||
|
|
|
@ -565,7 +565,14 @@
|
|||
|
||||
</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
|
||||
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.browser.common import schema_macros, schema_edit_macros
|
||||
from cybertools.composer.schema.schema import FormState
|
||||
from cybertools.stateful.interfaces import IStateful
|
||||
from cybertools.typology.interfaces import IType, ITypeManager
|
||||
from loops.common import adapted
|
||||
from loops.concept import Concept, ConceptRelation, ResourceRelation
|
||||
|
@ -437,18 +438,31 @@ class EditObject(FormController, I18NView):
|
|||
formState = instance.applyTemplate(data=form, fieldHandlers=self.fieldHandlers)
|
||||
self.selected = []
|
||||
self.old = []
|
||||
stateKeys = []
|
||||
for k in form.keys():
|
||||
if k.startswith(self.prefix):
|
||||
fn = k[len(self.prefix):]
|
||||
value = form[k]
|
||||
if fn.startswith(self.conceptPrefix) and value:
|
||||
self.collectConcepts(fn[len(self.conceptPrefix):], value)
|
||||
if k.startswith('state.'):
|
||||
stateKeys.append(k)
|
||||
self.collectAutoConcepts()
|
||||
if self.old or self.selected:
|
||||
self.assignConcepts(obj)
|
||||
for k in stateKeys:
|
||||
self.updateState(k)
|
||||
notify(ObjectModifiedEvent(obj))
|
||||
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):
|
||||
""" Special handler for fileupload fields;
|
||||
value is a FileUpload instance.
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
<tr metal:use-macro="customMacro" />
|
||||
</tal:custom>
|
||||
<tr metal:use-macro="view/template/macros/versioning" />
|
||||
<tr metal:use-macro="view/template/macros/states" />
|
||||
<tr metal:use-macro="view/template/macros/buttons" />
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -216,6 +217,34 @@
|
|||
</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">
|
||||
<td colspan="5">
|
||||
<input value="Save" type="submit"
|
||||
|
|
|
@ -175,6 +175,10 @@ div.menu-1, div.menu-2 {
|
|||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.icon-action {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.flow-left {
|
||||
float: left;
|
||||
}
|
||||
|
@ -251,7 +255,6 @@ table.listing th {
|
|||
padding: 1em 0 1em 0;
|
||||
}
|
||||
|
||||
|
||||
/* search stuff */
|
||||
|
||||
.searchForm input.button, input.submit {
|
||||
|
|
|
@ -383,11 +383,12 @@ class NodeView(BaseView):
|
|||
return ('%s/.target%s' %
|
||||
(self.url, util.getUidForObject(target)))
|
||||
|
||||
def getActions(self, category='object'):
|
||||
def getActions(self, category='object', target=None):
|
||||
actions = []
|
||||
self.registerDojo()
|
||||
if category in self.actions:
|
||||
actions.extend(self.actions[category](self))
|
||||
if target is None:
|
||||
target = self.virtualTarget
|
||||
if target is not None:
|
||||
actions.extend(target.getActions(category, page=self))
|
||||
|
@ -456,17 +457,23 @@ class NodeView(BaseView):
|
|||
url = self.virtualTargetUrl
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
""" Provides inline editor as inner HTML"""
|
||||
|
||||
|
|
|
@ -25,29 +25,6 @@
|
|||
</div>
|
||||
<div tal:define="target nocall:item/targetObjectView"
|
||||
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"
|
||||
tal:define="node nocall:item;
|
||||
item nocall:target"
|
||||
|
@ -208,6 +185,16 @@
|
|||
</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">
|
||||
<div class="menu-2">loops Development</div>
|
||||
</metal:actions>
|
||||
|
@ -230,6 +217,11 @@
|
|||
|
||||
<!-- inner HTML macros -->
|
||||
|
||||
<metal:info define-macro="object_info">
|
||||
<div>Object Information</div>
|
||||
</metal:info>
|
||||
|
||||
|
||||
<div metal:define-macro="inline_edit"
|
||||
class="content-1" id="1.body">
|
||||
<form action="." method="post" id="1.form">
|
||||
|
@ -262,4 +254,3 @@
|
|||
tal:attributes="src context/++resource++edit.gif" border="0" />
|
||||
</a>
|
||||
</metal:editlink>
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ from zope.security.proxy import removeSecurityProxy
|
|||
from zope.traversing.api import getName, getParent
|
||||
from zope.traversing.browser import absoluteURL
|
||||
|
||||
from cybertools.browser.action import actions
|
||||
from cybertools.typology.interfaces import IType
|
||||
from cybertools.xedit.browser import ExternalEditorView, fromUnicode
|
||||
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.interfaces import IBaseResource, IDocument, IMediaAsset, ITextDocument
|
||||
from loops.interfaces import ITypeConcept
|
||||
from loops.organize.stateful.browser import statefulActions
|
||||
from loops.versioning.browser import version_macros
|
||||
from loops.versioning.interfaces import IVersionable
|
||||
from loops.util import _
|
||||
|
@ -182,14 +184,11 @@ class ResourceView(BaseView):
|
|||
return actions
|
||||
|
||||
def getObjectActions(self, page=None):
|
||||
actions = []
|
||||
if page is None:
|
||||
factory, view = Action, self
|
||||
else:
|
||||
factory, view = TargetAction, page
|
||||
#if self.xeditable:
|
||||
# actions.append(factory(self, page=view,))
|
||||
return actions
|
||||
acts = ['info']
|
||||
acts.extend('state.' + st for st in statefulActions)
|
||||
if self.xeditable:
|
||||
acts.append('external_edit')
|
||||
return actions.get('object', acts, view=self, page=page)
|
||||
|
||||
actions = dict(portlet=getPortletActions, object=getObjectActions)
|
||||
|
||||
|
@ -209,6 +208,12 @@ class ResourceView(BaseView):
|
|||
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):
|
||||
|
||||
#def __init__(self, context, request):
|
||||
|
|
|
@ -2,10 +2,7 @@
|
|||
<div tal:attributes="ondblclick python: item.openEditWindow('configure.html')">
|
||||
<tal:body define="itemNum view/itemNum;
|
||||
id string:$itemNum.body;">
|
||||
<tal:edit define="target nocall:item;
|
||||
item nocall:node|nocall:view;">
|
||||
<div metal:use-macro="views/node_macros/editicons" />
|
||||
</tal:edit>
|
||||
<div metal:use-macro="views/node_macros/object_actions" />
|
||||
<h1 tal:content="item/title">Title</h1>
|
||||
<p tal:define="description description|item/description"
|
||||
tal:condition="description">
|
||||
|
@ -40,6 +37,7 @@
|
|||
|
||||
<metal:block define-macro="image">
|
||||
<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 />
|
||||
<img src="#"
|
||||
tal:attributes="src string:${view/url}/.target${view/targetId}/view" />
|
||||
|
@ -50,6 +48,7 @@
|
|||
|
||||
<metal:block define-macro="download">
|
||||
<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>
|
||||
<p><i tal:content="item/description">Description</i></p>
|
||||
<p>
|
||||
|
|
|
@ -64,6 +64,8 @@ not just kept in the adapter.
|
|||
Controlling classification quality
|
||||
----------------------------------
|
||||
|
||||
We again first have to register states definitions and adapter classes.
|
||||
|
||||
>>> from loops.organize.stateful.quality import classificationQuality
|
||||
>>> component.provideUtility(classificationQuality(),
|
||||
... name='loops.classification_quality')
|
||||
|
@ -74,10 +76,15 @@ Controlling classification quality
|
|||
>>> component.provideHandler(assign)
|
||||
>>> component.provideHandler(deassign)
|
||||
|
||||
Now we can get a stateful adapter for a resource.
|
||||
|
||||
>>> qcheckedDoc01 = component.getAdapter(doc01, IStateful,
|
||||
... name='loops.classification_quality')
|
||||
>>> qcheckedDoc01.state
|
||||
'unclassified'
|
||||
'new'
|
||||
|
||||
Let's create two customer objects to be used for classification of resources
|
||||
later.
|
||||
|
||||
>>> tCustomer = concepts['customer']
|
||||
>>> from loops.concept import Concept
|
||||
|
@ -87,6 +94,9 @@ Controlling classification quality
|
|||
>>> c02 = addAndConfigureObject(concepts, Concept, 'c02', conceptType=tCustomer,
|
||||
... title='DocFive')
|
||||
|
||||
When we change the concept assignments of the resource - i.e. its classification
|
||||
- the classification quality state changes automaticalls
|
||||
|
||||
>>> c01.assignResource(doc01)
|
||||
>>> qcheckedDoc01 = component.getAdapter(doc01, IStateful,
|
||||
... name='loops.classification_quality')
|
||||
|
@ -97,10 +107,15 @@ Controlling classification quality
|
|||
>>> qcheckedDoc01.state
|
||||
'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.state
|
||||
'verified'
|
||||
|
||||
Upon later changes of classification the "verified" state gets lost again.
|
||||
|
||||
>>> c02.deassignResource(doc01)
|
||||
>>> qcheckedDoc01.state
|
||||
'classified'
|
||||
|
@ -109,6 +124,39 @@ Controlling classification quality
|
|||
>>> qcheckedDoc01.state
|
||||
'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
|
||||
=============
|
||||
|
|
|
@ -41,6 +41,9 @@ class StatefulLoopsObject(Stateful, StatefulAdapter):
|
|||
|
||||
adapts(ILoopsObject)
|
||||
|
||||
def getAvailableTransitionsForUser(self):
|
||||
return self.getAvailableTransitions()
|
||||
|
||||
|
||||
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)
|
||||
def 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',
|
||||
('verify', 'change_classification', 'remove_classification')),
|
||||
('verify', 'change_classification', 'remove_classification'),
|
||||
color='yellow'),
|
||||
State('verified', 'verified',
|
||||
('change_classification', 'remove_classification')),
|
||||
('change_classification', 'remove_classification'),
|
||||
color='green'),
|
||||
Transition('classify', 'classify', 'classified'),
|
||||
Transition('verify', 'verify', 'verified'),
|
||||
Transition('change_classification', 'change classification', 'classified'),
|
||||
Transition('remove_classification', 'remove classification', 'unclassified'),
|
||||
initialState='unclassified')
|
||||
initialState='new')
|
||||
|
||||
|
||||
class ClassificationQualityCheckable(StatefulLoopsObject):
|
||||
|
||||
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
|
||||
|
||||
@adapter(ILoopsObject, IAssignmentEvent)
|
||||
def assign(obj, event):
|
||||
target = event.relation.second
|
||||
if not IResource.providedBy(target):
|
||||
return
|
||||
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')
|
||||
stf = component.getAdapter(event.relation.second, IStateful,
|
||||
name='loops.classification_quality')
|
||||
stf.assign(event.relation)
|
||||
|
||||
@adapter(ILoopsObject, IDeassignmentEvent)
|
||||
def deassign(obj, event):
|
||||
target = event.relation.second
|
||||
if not IResource.providedBy(target):
|
||||
return
|
||||
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')
|
||||
stf = component.getAdapter(event.relation.second, IStateful,
|
||||
name='loops.classification_quality')
|
||||
stf.deassign(event.relation)
|
||||
|
|
Loading…
Add table
Reference in a new issue