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

View file

@ -368,8 +368,8 @@ class ZopeInstaller:
constructors = (ctor,), constructors = (ctor,),
permission = self.addContentPermissions[name]) permission = self.addContentPermissions[name])
# Create workflow prototypical instances in __instance__ attributes # Create workflow prototypical instances in __instance__ attributes
wf = getattr(klass.wrapperClass, 'workflow', None) wf = wrapper.getWorkflow()
if wf and not hasattr(wf, '__instance__'): wf.__instance__ = wf() if not hasattr(wf, '__instance__'): wf.__instance__ = wf()
def installAppyTypes(self): def installAppyTypes(self):
'''We complete here the initialisation process of every Appy type of '''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 elem is the one-line user info as shown on every page; second line is
the URL to edit user info.''' the URL to edit user info.'''
appyUser = self.appy().appyUser appyUser = self.appy().appyUser
res = [appyUser.title] info = [appyUser.title]
rolesToShow = [r for r in appyUser.roles \ rolesToShow = [r for r in appyUser.roles \
if r not in ('Authenticated', 'Member')] if r not in ('Authenticated', 'Member')]
if rolesToShow: if rolesToShow:
res.append(', '.join([self.translate('role_%s'%r) \ info.append(', '.join([self.translate('role_%s'%r) \
for r in rolesToShow])) for r in rolesToShow]))
return (' | '.join(res), appyUser.o.getUrl(mode='edit', page='main', # Edit URL for the appy user.
nav='')) url = None
if appyUser.o.mayEdit():
url = appyUser.o.getUrl(mode='edit', page='main', nav='')
return (' | '.join(info), url)
def generateUid(self, className): def generateUid(self, className):
'''Generates a UID for an instance of p_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 '''Returns the workflow applicable for p_self (or for any instance of
p_className if given), or its name, if p_name is True.''' p_className if given), or its name, if p_name is True.'''
if not className: if not className:
appyClass = self.wrapperClass.__bases__[-1] wrapperClass = self.wrapperClass
else: else:
appyClass = self.getTool().getAppyClass(className) wrapperClass = self.getTool().getAppyClass(className, wrapper=True)
if hasattr(appyClass, 'workflow'): wf = appyClass.workflow wf = wrapperClass.getWorkflow()
else:
wf = gen.WorkflowAnonymous
if not name: return wf if not name: return wf
return WorkflowDescriptor.getWorkflowName(wf) return WorkflowDescriptor.getWorkflowName(wf)
@ -957,13 +955,23 @@ class BaseMixin:
return True return True
def mayDelete(self): def mayDelete(self):
'''May the currently logged user delete this object? This condition '''May the currently logged user delete this object?.'''
comes as an addition/refinement to the corresponding workflow res = self.allows('Delete objects')
permission.''' if not res: return
# An additional, user-defined condition, may refine the base permission.
appyObj = self.appy() appyObj = self.appy()
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete() if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
return True 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): def executeAppyAction(self, actionName, reindex=True):
'''Executes action with p_fieldName on this object.''' '''Executes action with p_fieldName on this object.'''
appyType = self.getAppyType(actionName) appyType = self.getAppyType(actionName)

View file

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

View file

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

View file

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

View file

@ -40,7 +40,7 @@
</tal:moveRef> </tal:moveRef>
</td> </td>
<tal:comment replace="nothing">Edit the element</tal:comment> <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);" <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)"> tal:attributes="href python: obj.getUrl(mode='edit', page='main', nav=navInfo)">
<img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/> <img title="Edit" tal:attributes="src string: $appUrl/ui/edit.gif"/>
@ -48,7 +48,7 @@
</td> </td>
<tal:comment replace="nothing">Delete the element</tal:comment> <tal:comment replace="nothing">Delete the element</tal:comment>
<td> <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" title="Delete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png; tal:attributes="src string: $appUrl/ui/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/> onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>

View file

@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, mimetypes import os, os.path, mimetypes
import appy.pod 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.gen.utils import createObject
from appy.shared.utils import getOsTempFolder, executeCommand, \ from appy.shared.utils import getOsTempFolder, executeCommand, \
normalizeString, sequenceTypes normalizeString, sequenceTypes
@ -87,6 +87,21 @@ class AbstractWrapper(object):
return customUser.__dict__[methodName](self, *args, **kwargs) return customUser.__dict__[methodName](self, *args, **kwargs)
def getField(self, name): return self.o.getAppyType(name) 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): def link(self, fieldName, obj):
'''This method links p_obj (which can be a list of objects) to this one '''This method links p_obj (which can be a list of objects) to this one