[gen] added obj.mayEdit, an additional condition for editing an object (similar to mayDelete); bugfix: specifying a workflow for a User class crashed because, in installer.py, Appy took into account the standard workflow on this Class instead of the custom one.

This commit is contained in:
Gaetan Delannay 2012-06-01 15:57:19 +02:00
parent e3b7f5364f
commit 0d7afb685f
9 changed files with 49 additions and 25 deletions

View file

@ -2791,7 +2791,6 @@ class WorkflowAnonymous:
o = 'Owner'
active = State({r:(mgr, 'Anonymous', 'Authenticated'), w:(mgr,o),d:(mgr,o)},
initial=True)
WorkflowAnonymous.__instance__ = WorkflowAnonymous()
class WorkflowAuthenticated:
'''One-state workflow allowing authenticated users to consult and Manager
@ -2800,7 +2799,6 @@ class WorkflowAuthenticated:
o = 'Owner'
active = State({r:(mgr, 'Authenticated'), w:(mgr,o), d:(mgr,o)},
initial=True)
WorkflowAuthenticated.__instance__ = WorkflowAuthenticated()
class WorkflowOwner:
'''One-state workflow allowing only manager and owner to consult and
@ -2808,7 +2806,6 @@ class WorkflowOwner:
mgr = 'Manager'
o = 'Owner'
active = State({r:(mgr, o), w:(mgr, o), d:mgr}, initial=True)
WorkflowOwner.__instance__ = WorkflowOwner()
# ------------------------------------------------------------------------------
class Selection:

View file

@ -368,8 +368,8 @@ class ZopeInstaller:
constructors = (ctor,),
permission = self.addContentPermissions[name])
# Create workflow prototypical instances in __instance__ attributes
wf = getattr(klass.wrapperClass, 'workflow', None)
if wf and not hasattr(wf, '__instance__'): wf.__instance__ = wf()
wf = wrapper.getWorkflow()
if not hasattr(wf, '__instance__'): wf.__instance__ = wf()
def installAppyTypes(self):
'''We complete here the initialisation process of every Appy type of

View file

@ -992,14 +992,17 @@ class ToolMixin(BaseMixin):
elem is the one-line user info as shown on every page; second line is
the URL to edit user info.'''
appyUser = self.appy().appyUser
res = [appyUser.title]
info = [appyUser.title]
rolesToShow = [r for r in appyUser.roles \
if r not in ('Authenticated', 'Member')]
if rolesToShow:
res.append(', '.join([self.translate('role_%s'%r) \
for r in rolesToShow]))
return (' | '.join(res), appyUser.o.getUrl(mode='edit', page='main',
nav=''))
info.append(', '.join([self.translate('role_%s'%r) \
for r in rolesToShow]))
# Edit URL for the appy user.
url = None
if appyUser.o.mayEdit():
url = appyUser.o.getUrl(mode='edit', page='main', nav='')
return (' | '.join(info), url)
def generateUid(self, className):
'''Generates a UID for an instance of p_className.'''

View file

@ -879,12 +879,10 @@ class BaseMixin:
'''Returns the workflow applicable for p_self (or for any instance of
p_className if given), or its name, if p_name is True.'''
if not className:
appyClass = self.wrapperClass.__bases__[-1]
wrapperClass = self.wrapperClass
else:
appyClass = self.getTool().getAppyClass(className)
if hasattr(appyClass, 'workflow'): wf = appyClass.workflow
else:
wf = gen.WorkflowAnonymous
wrapperClass = self.getTool().getAppyClass(className, wrapper=True)
wf = wrapperClass.getWorkflow()
if not name: return wf
return WorkflowDescriptor.getWorkflowName(wf)
@ -957,13 +955,23 @@ class BaseMixin:
return True
def mayDelete(self):
'''May the currently logged user delete this object? This condition
comes as an addition/refinement to the corresponding workflow
permission.'''
'''May the currently logged user delete this object?.'''
res = self.allows('Delete objects')
if not res: return
# An additional, user-defined condition, may refine the base permission.
appyObj = self.appy()
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
return True
def mayEdit(self):
'''May the currently logged user edit this object?.'''
res = self.allows('Modify portal content')
if not res: return
# An additional, user-defined condition, may refine the base permission.
appyObj = self.appy()
if hasattr(appyObj, 'mayEdit'): return appyObj.mayEdit()
return True
def executeAppyAction(self, actionName, reindex=True):
'''Executes action with p_fieldName on this object.'''
appyType = self.getAppyType(actionName)

View file

@ -285,7 +285,7 @@
<img title="Edit" style="cursor:pointer"
tal:attributes="onClick python: 'href: window.location=\'%s\'' % contextObj.getUrl(mode='edit', page=page);
src string: $appUrl/ui/editBig.png"
tal:condition="python: contextObj.allows('Modify portal content')"/>
tal:condition="contextObj/mayEdit"/>
</tal:edit>
<tal:refresh condition="contextObj/isDebug">

View file

@ -124,12 +124,12 @@
<td>
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)"
tal:condition="python: obj.allows('Modify portal content')">
tal:condition="obj/mayEdit">
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
</a></td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td>
<img tal:condition="python: obj.allows('Delete objects') and obj.mayDelete()"
<img tal:condition="obj/mayDelete"
title="Delete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>

View file

@ -143,7 +143,8 @@
</td>
<td align="right" class="userStripText" tal:define="userInfo tool/getUserLine">
<span tal:content="python: userInfo[0]"></span>
<a tal:attributes="href python: userInfo[1]">
<a tal:condition="python: userInfo[1]"
tal:attributes="href python: userInfo[1]">
<img tal:attributes="src string: $appUrl/ui/edit.gif"/>
</a>
</td>

View file

@ -40,7 +40,7 @@
</tal:moveRef>
</td>
<tal:comment replace="nothing">Edit the element</tal:comment>
<td tal:condition="python: obj.allows('Modify portal content') and not appyType['noForm']">
<td tal:condition="python: not appyType['noForm'] and obj.mayEdit()">
<a tal:define="navInfo python:'ref.%s.%s:%s.%d.%d' % (contextObj.UID(), fieldName, appyType['pageName'], repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)">
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
@ -48,7 +48,7 @@
</td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td>
<img tal:condition="python: not appyType['isBack'] and obj.allows('Delete objects') and obj.mayDelete()"
<img tal:condition="python: not appyType['isBack'] and obj.mayDelete()"
title="Delete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>

View file

@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
import os, os.path, mimetypes
import appy.pod
from appy.gen import Type, Search, Ref, String
from appy.gen import Type, Search, Ref, String, WorkflowAnonymous
from appy.gen.utils import createObject
from appy.shared.utils import getOsTempFolder, executeCommand, \
normalizeString, sequenceTypes
@ -87,6 +87,21 @@ class AbstractWrapper(object):
return customUser.__dict__[methodName](self, *args, **kwargs)
def getField(self, name): return self.o.getAppyType(name)
@classmethod
def getWorkflow(klass):
'''Returns the workflow tied to p_klass.'''
# Browse parent classes of p_klass in reverse order. This way, a
# user-defined workflow will override a Appy default workflow.
i = len(klass.__bases__)-1
res = None
while i >= 0:
res = getattr(klass.__bases__[i], 'workflow', None)
if res: break
i -= 1
# Return a default workflow if no workflow was found.
if not res:
res = WorkflowAnonymous
return res
def link(self, fieldName, obj):
'''This method links p_obj (which can be a list of objects) to this one