[gen] Security improvements.
This commit is contained in:
		
							parent
							
								
									b2dbef2bc4
								
							
						
					
					
						commit
						5c6a7f0f97
					
				
					 9 changed files with 146 additions and 120 deletions
				
			
		|  | @ -307,9 +307,9 @@ class Field: | ||||||
|         '''When displaying p_obj on a given p_layoutType, must we show this |         '''When displaying p_obj on a given p_layoutType, must we show this | ||||||
|            field?''' |            field?''' | ||||||
|         # Check if the user has the permission to view or edit the field |         # Check if the user has the permission to view or edit the field | ||||||
|         if layoutType == 'edit': perm = self.writePermission |         perm = (layoutType == 'edit') and self.writePermission or \ | ||||||
|         else:                    perm = self.readPermission |                                           self.readPermission | ||||||
|         if not obj.allows(perm): return False |         if not obj.allows(perm): return | ||||||
|         # Evaluate self.show |         # Evaluate self.show | ||||||
|         if callable(self.show): |         if callable(self.show): | ||||||
|             res = self.callMethod(obj, self.show) |             res = self.callMethod(obj, self.show) | ||||||
|  | @ -319,7 +319,7 @@ class Field: | ||||||
|         if type(res) in sutils.sequenceTypes: |         if type(res) in sutils.sequenceTypes: | ||||||
|             for r in res: |             for r in res: | ||||||
|                 if r == layoutType: return True |                 if r == layoutType: return True | ||||||
|             return False |             return | ||||||
|         elif res in ('view', 'edit', 'result'): |         elif res in ('view', 'edit', 'result'): | ||||||
|             return res == layoutType |             return res == layoutType | ||||||
|         return bool(res) |         return bool(res) | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ class Calendar(Field): | ||||||
|     # Month view for a calendar. Called by pxView, and directly from the UI, |     # Month view for a calendar. Called by pxView, and directly from the UI, | ||||||
|     # via Ajax, when the user selects another month. |     # via Ajax, when the user selects another month. | ||||||
|     pxMonthView = Px(''' |     pxMonthView = Px(''' | ||||||
|      <div var="ajaxHookId=zobj.UID() + field.name; |      <div var="ajaxHookId=zobj.id + field.name; | ||||||
|                month=req['month']; |                month=req['month']; | ||||||
|                monthDayOne=DateTime('%s/01' % month); |                monthDayOne=DateTime('%s/01' % month); | ||||||
|                today=DateTime('00:00'); |                today=DateTime('00:00'); | ||||||
|  | @ -27,7 +27,7 @@ class Calendar(Field): | ||||||
|                defaultDateMonth=defaultDate.strftime('%Y/%m'); |                defaultDateMonth=defaultDate.strftime('%Y/%m'); | ||||||
|                previousMonth=field.getSiblingMonth(month, 'previous'); |                previousMonth=field.getSiblingMonth(month, 'previous'); | ||||||
|                nextMonth=field.getSiblingMonth(month, 'next'); |                nextMonth=field.getSiblingMonth(month, 'next'); | ||||||
|                mayEdit=zobj.allows(field.writePermission); |                mayEdit=zobj.mayEdit(field.writePermission); | ||||||
|                objUrl=zobj.absolute_url(); |                objUrl=zobj.absolute_url(); | ||||||
|                startDate=field.getStartDate(zobj); |                startDate=field.getStartDate(zobj); | ||||||
|                endDate=field.getEndDate(zobj); |                endDate=field.getEndDate(zobj); | ||||||
|  | @ -630,6 +630,8 @@ class Calendar(Field): | ||||||
|            or deletion of a calendar event.''' |            or deletion of a calendar event.''' | ||||||
|         rq = obj.REQUEST |         rq = obj.REQUEST | ||||||
|         action = rq['actionType'] |         action = rq['actionType'] | ||||||
|  |         # Security check | ||||||
|  |         obj.mayEdit(self.writePermission, raiseError=True) | ||||||
|         # Get the date for this action |         # Get the date for this action | ||||||
|         if action == 'createEvent': |         if action == 'createEvent': | ||||||
|             return self.createEvent(obj, DateTime(rq['day'])) |             return self.createEvent(obj, DateTime(rq['day'])) | ||||||
|  |  | ||||||
|  | @ -468,7 +468,7 @@ class Pod(Field): | ||||||
|         # What is the action to perform? |         # What is the action to perform? | ||||||
|         action = rq.get('action', 'generate') |         action = rq.get('action', 'generate') | ||||||
|         # Security check. |         # Security check. | ||||||
|         obj.o.allows('read', raiseError=True) |         obj.o.mayView(self.readPermission, raiseError=True) | ||||||
|         # Perform the requested action. |         # Perform the requested action. | ||||||
|         tool = obj.tool.o |         tool = obj.tool.o | ||||||
|         template = rq.get('template') |         template = rq.get('template') | ||||||
|  | @ -486,7 +486,7 @@ class Pod(Field): | ||||||
|             res.writeResponse(rq.RESPONSE) |             res.writeResponse(rq.RESPONSE) | ||||||
|             return |             return | ||||||
|         # Performing any other action requires write access to p_obj. |         # Performing any other action requires write access to p_obj. | ||||||
|         obj.o.allows('write', raiseError=True) |         obj.o.mayEdit(self.writePermission, raiseError=True) | ||||||
|         msg = 'action_done' |         msg = 'action_done' | ||||||
|         if action == 'freeze': |         if action == 'freeze': | ||||||
|             # (Re-)freeze a document in the database. |             # (Re-)freeze a document in the database. | ||||||
|  |  | ||||||
|  | @ -70,7 +70,7 @@ class Ref(Field): | ||||||
|             style=":'%s; %s' % (url(imgName, bg=True), \ |             style=":'%s; %s' % (url(imgName, bg=True), \ | ||||||
|                                 ztool.getButtonWidth(label))"/> |                                 ztool.getButtonWidth(label))"/> | ||||||
|      <!-- Delete several objects --> |      <!-- Delete several objects --> | ||||||
|      <input if="not isBack and field.delete and canWrite" |      <input if="mayEdit and field.delete" | ||||||
|             var2="action='delete'; label=_('object_delete_many')" |             var2="action='delete'; label=_('object_delete_many')" | ||||||
|             type="button" class="button" value=":label" |             type="button" class="button" value=":label" | ||||||
|             onclick=":'onLinkMany(%s,%s)' % (q(action), q(ajaxHookId))" |             onclick=":'onLinkMany(%s,%s)' % (q(action), q(ajaxHookId))" | ||||||
|  | @ -84,8 +84,7 @@ class Ref(Field): | ||||||
|      <table class="noStyle"> |      <table class="noStyle"> | ||||||
|       <tr> |       <tr> | ||||||
|        <!-- Arrows for moving objects up or down --> |        <!-- Arrows for moving objects up or down --> | ||||||
|        <td if="not isBack and (totalNumber >1) and changeOrder and canWrite \ |        <td if="(totalNumber >1) and changeOrder and not inPickList" | ||||||
|                and not inPickList" |  | ||||||
|           var2="ajaxBaseCall=navBaseCall.replace('**v**','%s,%s,{%s:%s,%s:%s}'%\ |           var2="ajaxBaseCall=navBaseCall.replace('**v**','%s,%s,{%s:%s,%s:%s}'%\ | ||||||
|                   (q(startNumber), q('doChangeOrder'), q('refObjectUid'), |                   (q(startNumber), q('doChangeOrder'), q('refObjectUid'), | ||||||
|                    q(tiedUid), q('move'), q('**v**')))"> |                    q(tiedUid), q('move'), q('**v**')))"> | ||||||
|  | @ -114,7 +113,7 @@ class Ref(Field): | ||||||
|          <img src=":url('edit')" title=":_('object_edit')"/></a> |          <img src=":url('edit')" title=":_('object_edit')"/></a> | ||||||
|        </td> |        </td> | ||||||
|        <!-- Delete --> |        <!-- Delete --> | ||||||
|        <td if="not isBack and field.delete and canWrite and tied.o.mayDelete()"> |        <td if="mayEdit and field.delete and tied.o.mayDelete()"> | ||||||
|         <img class="clickable" title=":_('object_delete')" src=":url('delete')" |         <img class="clickable" title=":_('object_delete')" src=":url('delete')" | ||||||
|              onclick=":'onDeleteObject(%s)' % q(tiedUid)"/> |              onclick=":'onDeleteObject(%s)' % q(tiedUid)"/> | ||||||
|        </td> |        </td> | ||||||
|  | @ -141,7 +140,7 @@ class Ref(Field): | ||||||
|     # Displays the button allowing to add a new object through a Ref field, if |     # Displays the button allowing to add a new object through a Ref field, if | ||||||
|     # it has been declared as addable and if multiplicities allow it. |     # it has been declared as addable and if multiplicities allow it. | ||||||
|     pxAdd = Px(''' |     pxAdd = Px(''' | ||||||
|       <input if="showPlusIcon and not inPickList" type="button" |       <input if="mayAdd and not inPickList" type="button" | ||||||
|              class="buttonSmall button" |              class="buttonSmall button" | ||||||
|         var2="navInfo='ref.%s.%s:%s.%d.%d' % (zobj.id, field.name, \ |         var2="navInfo='ref.%s.%s:%s.%d.%d' % (zobj.id, field.name, \ | ||||||
|                                               field.pageName, 0, totalNumber); |                                               field.pageName, 0, totalNumber); | ||||||
|  | @ -163,8 +162,7 @@ class Ref(Field): | ||||||
|     # This PX displays, in a cell header from a ref table, icons for sorting the |     # This PX displays, in a cell header from a ref table, icons for sorting the | ||||||
|     # ref field according to the field that corresponds to this column. |     # ref field according to the field that corresponds to this column. | ||||||
|     pxSortIcons = Px(''' |     pxSortIcons = Px(''' | ||||||
|      <x if="changeOrder and canWrite and ztool.isSortable(refField.name, \ |      <x if="changeOrder and ztool.isSortable(refField.name,tiedClassName,'ref')" | ||||||
|             tiedClassName, 'ref')" |  | ||||||
|         var2="ajaxBaseCall=navBaseCall.replace('**v**', '%s,%s,{%s:%s,%s:%s}'% \ |         var2="ajaxBaseCall=navBaseCall.replace('**v**', '%s,%s,{%s:%s,%s:%s}'% \ | ||||||
|                (q(startNumber), q('sort'), q('sortKey'), q(refField.name), \ |                (q(startNumber), q('sort'), q('sortKey'), q(refField.name), \ | ||||||
|                 q('reverse'), q('**v**')))"> |                 q('reverse'), q('**v**')))"> | ||||||
|  | @ -195,7 +193,7 @@ class Ref(Field): | ||||||
| 
 | 
 | ||||||
|     # PX that displays referred objects as a list. |     # PX that displays referred objects as a list. | ||||||
|     pxViewList = Px(''' |     pxViewList = Px(''' | ||||||
|      <div if="not innerRef or showPlusIcon" style="margin-bottom: 4px"> |      <div if="not innerRef or mayAdd" style="margin-bottom: 4px"> | ||||||
|       <span if="subLabel" class="discreet">:_(subLabel)</span> |       <span if="subLabel" class="discreet">:_(subLabel)</span> | ||||||
|       (<span class="discreet">:totalNumber</span>)  |       (<span class="discreet">:totalNumber</span>)  | ||||||
|       <x>:field.pxAdd</x> |       <x>:field.pxAdd</x> | ||||||
|  | @ -215,7 +213,7 @@ class Ref(Field): | ||||||
| 
 | 
 | ||||||
|      <!-- No object is present --> |      <!-- No object is present --> | ||||||
|      <p class="discreet" |      <p class="discreet" | ||||||
|         if="not objects and (innerRef and showPlusIcon)">:_('no_ref')</p> |         if="not objects and (innerRef and mayAdd)">:_('no_ref')</p> | ||||||
| 
 | 
 | ||||||
|      <!-- Linked objects --> |      <!-- Linked objects --> | ||||||
|      <table if="objects" class=":not innerRef and 'list' or ''" |      <table if="objects" class=":not innerRef and 'list' or ''" | ||||||
|  | @ -242,7 +240,7 @@ class Ref(Field): | ||||||
|           class=":loop.tied.odd and 'even' or 'odd'" |           class=":loop.tied.odd and 'even' or 'odd'" | ||||||
|           var2="tiedUid=tied.o.id; |           var2="tiedUid=tied.o.id; | ||||||
|                 objectIndex=field.getIndexOf(zobj, tiedUid)|None; |                 objectIndex=field.getIndexOf(zobj, tiedUid)|None; | ||||||
|                 mayView=tied.allows('read')"> |                 mayView=tied.o.mayView()"> | ||||||
|        <td if="not inPickList and numbered">:field.pxNumber</td> |        <td if="not inPickList and numbered">:field.pxNumber</td> | ||||||
|        <td for="column in columns" width=":column.width" align=":column.align" |        <td for="column in columns" width=":column.width" align=":column.align" | ||||||
|            var2="refField=column.field"> |            var2="refField=column.field"> | ||||||
|  | @ -263,15 +261,15 @@ class Ref(Field): | ||||||
|             if="field.isShowable(zobj, 'result')">:field.pxRender</x> |             if="field.isShowable(zobj, 'result')">:field.pxRender</x> | ||||||
|         </x> |         </x> | ||||||
|        </td> |        </td> | ||||||
|        <td if="checkboxes and mayView" class="cbCell"> |        <td if="checkboxes" class="cbCell"> | ||||||
|         <input type="checkbox" name=":ajaxHookId" checked="checked" |         <input if="mayView" type="checkbox" name=":ajaxHookId" checked="checked" | ||||||
|                value=":tiedUid" onclick="toggleRefCb(this)"/> |                value=":tiedUid" onclick="toggleRefCb(this)"/> | ||||||
|        </td> |        </td> | ||||||
|       </tr> |       </tr> | ||||||
|      </table> |      </table> | ||||||
| 
 | 
 | ||||||
|      <!-- Global actions --> |      <!-- Global actions --> | ||||||
|      <div if="canWrite and (totalNumber > 1)" |      <div if="mayEdit and (totalNumber > 1)" | ||||||
|           align=":dright">:field.pxGlobalActions</div> |           align=":dright">:field.pxGlobalActions</div> | ||||||
| 
 | 
 | ||||||
|      <!-- (Bottom) navigation --> |      <!-- (Bottom) navigation --> | ||||||
|  | @ -288,7 +286,6 @@ class Ref(Field): | ||||||
|      <x var="innerRef=False; |      <x var="innerRef=False; | ||||||
|              ajaxHookId=ajaxHookId|'%s_%s_poss' % (zobj.id, field.name); |              ajaxHookId=ajaxHookId|'%s_%s_poss' % (zobj.id, field.name); | ||||||
|              inPickList=True; |              inPickList=True; | ||||||
|              isBack=field.isBack; |  | ||||||
|              startNumber=field.getStartNumber('list', req, ajaxHookId); |              startNumber=field.getStartNumber('list', req, ajaxHookId); | ||||||
|              info=field.getPossibleValues(zobj, startNumber=startNumber, \ |              info=field.getPossibleValues(zobj, startNumber=startNumber, \ | ||||||
|                                           someObjects=True, removeLinked=True); |                                           someObjects=True, removeLinked=True); | ||||||
|  | @ -297,10 +294,10 @@ class Ref(Field): | ||||||
|              batchSize=info.batchSize; |              batchSize=info.batchSize; | ||||||
|              batchNumber=len(objects); |              batchNumber=len(objects); | ||||||
|              tiedClassName=tiedClassName|ztool.getPortalType(field.klass); |              tiedClassName=tiedClassName|ztool.getPortalType(field.klass); | ||||||
|              canWrite=canWrite|\ |              mayEdit=mayEdit|\ | ||||||
|                       not field.isBack and zobj.allows(field.writePermission); |                      not field.isBack and zobj.mayEdit(field.writePermission); | ||||||
|              mayUnlink=False; |              mayUnlink=False; | ||||||
|              showPlusIcon=False; |              mayAdd=False; | ||||||
|              navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \ |              navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \ | ||||||
|                           (q(ajaxHookId), q(zobj.absolute_url()), \ |                           (q(ajaxHookId), q(zobj.absolute_url()), \ | ||||||
|                            q(field.name), q(innerRef)); |                            q(field.name), q(innerRef)); | ||||||
|  | @ -360,7 +357,6 @@ class Ref(Field): | ||||||
|              linkList=field.link == 'list'; |              linkList=field.link == 'list'; | ||||||
|              renderAll=req.get('scope') != 'objs'; |              renderAll=req.get('scope') != 'objs'; | ||||||
|              inPickList=False; |              inPickList=False; | ||||||
|              isBack=field.isBack; |  | ||||||
|              startNumber=field.getStartNumber(render, req, ajaxHookId); |              startNumber=field.getStartNumber(render, req, ajaxHookId); | ||||||
|              info=field.getValue(zobj,startNumber=startNumber,someObjects=True); |              info=field.getValue(zobj,startNumber=startNumber,someObjects=True); | ||||||
|              objects=info.objects; |              objects=info.objects; | ||||||
|  | @ -370,19 +366,18 @@ class Ref(Field): | ||||||
|              batchNumber=len(objects); |              batchNumber=len(objects); | ||||||
|              folder=zobj.getCreateFolder(); |              folder=zobj.getCreateFolder(); | ||||||
|              tiedClassName=ztool.getPortalType(field.klass); |              tiedClassName=ztool.getPortalType(field.klass); | ||||||
|              canWrite=not field.isBack and zobj.allows(field.writePermission); |              mayEdit=not field.isBack and zobj.mayEdit(field.writePermission); | ||||||
|              mayUnlink=not isBack and canWrite and \ |              mayUnlink=mayEdit and field.getAttribute(zobj, 'unlink'); | ||||||
|                        field.getAttribute(zobj, 'unlink'); |              mayAdd=mayEdit and field.mayAdd(zobj, checkMayEdit=False); | ||||||
|              showPlusIcon=field.mayAdd(zobj); |  | ||||||
|              addConfirmMsg=field.addConfirm and \ |              addConfirmMsg=field.addConfirm and \ | ||||||
|                            _('%s_addConfirm' % field.labelId) or ''; |                            _('%s_addConfirm' % field.labelId) or ''; | ||||||
|              navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \ |              navBaseCall='askRefField(%s,%s,%s,%s,**v**)' % \ | ||||||
|                           (q(ajaxHookId), q(zobj.absolute_url()), \ |                           (q(ajaxHookId), q(zobj.absolute_url()), \ | ||||||
|                            q(field.name), q(innerRef)); |                            q(field.name), q(innerRef)); | ||||||
|              changeOrder=field.getAttribute(zobj, 'changeOrder'); |              changeOrder=mayEdit and field.getAttribute(zobj, 'changeOrder'); | ||||||
|              numbered=field.isNumbered(zobj); |              numbered=field.isNumbered(zobj); | ||||||
|              changeNumber=not inPickList and numbered and canWrite and \ |              changeNumber=not inPickList and numbered and changeOrder and \ | ||||||
|                           changeOrder and (totalNumber > 3); |                           (totalNumber > 3); | ||||||
|              checkboxesEnabled=field.getAttribute(zobj, 'checkboxes') and \ |              checkboxesEnabled=field.getAttribute(zobj, 'checkboxes') and \ | ||||||
|                                (layoutType != 'cell'); |                                (layoutType != 'cell'); | ||||||
|              checkboxes=checkboxesEnabled and (totalNumber > 1); |              checkboxes=checkboxesEnabled and (totalNumber > 1); | ||||||
|  | @ -390,7 +385,7 @@ class Ref(Field): | ||||||
|       <!-- JS tables storing checkbox statuses if checkboxes are enabled --> |       <!-- JS tables storing checkbox statuses if checkboxes are enabled --> | ||||||
|       <script if="checkboxesEnabled and renderAll" |       <script if="checkboxesEnabled and renderAll" | ||||||
|               type="text/javascript">:field.getCbJsInit(zobj)</script> |               type="text/javascript">:field.getCbJsInit(zobj)</script> | ||||||
|       <div if="linkList and renderAll and canWrite" |       <div if="linkList and renderAll and mayEdit" | ||||||
|            var2="ajaxHookId='%s_%s_poss' % (zobj.id, field.name)" |            var2="ajaxHookId='%s_%s_poss' % (zobj.id, field.name)" | ||||||
|            id=":ajaxHookId">:field.pxViewPickList</div> |            id=":ajaxHookId">:field.pxViewPickList</div> | ||||||
|       <x if="render == 'list'" |       <x if="render == 'list'" | ||||||
|  | @ -602,10 +597,10 @@ class Ref(Field): | ||||||
|         # We add here specific Ref rules for preventing to show the field under |         # We add here specific Ref rules for preventing to show the field under | ||||||
|         # some inappropriate circumstances. |         # some inappropriate circumstances. | ||||||
|         if layoutType == 'edit': |         if layoutType == 'edit': | ||||||
|             if self.mayAdd(obj): return False |             if self.mayAdd(obj): return | ||||||
|             if self.link in (False, 'list'): return False |             if self.link in (False, 'list'): return | ||||||
|         if self.isBack: |         if self.isBack: | ||||||
|             if layoutType == 'edit': return False |             if layoutType == 'edit': return | ||||||
|             else: return getattr(obj.aq_base, self.name, None) |             else: return getattr(obj.aq_base, self.name, None) | ||||||
|         return res |         return res | ||||||
| 
 | 
 | ||||||
|  | @ -835,7 +830,7 @@ class Ref(Field): | ||||||
|         '''This method links p_value (which can be a list of objects) to p_obj |         '''This method links p_value (which can be a list of objects) to p_obj | ||||||
|            through this Ref field.''' |            through this Ref field.''' | ||||||
|         # Security check |         # Security check | ||||||
|         if not noSecurity: obj.allows(self.writePermission, raiseError=True) |         if not noSecurity: obj.mayEdit(self.writePermission, raiseError=True) | ||||||
|         # p_value can be a list of objects |         # p_value can be a list of objects | ||||||
|         if type(value) in sutils.sequenceTypes: |         if type(value) in sutils.sequenceTypes: | ||||||
|             for v in value: self.linkObject(obj, v, back=back) |             for v in value: self.linkObject(obj, v, back=back) | ||||||
|  | @ -865,7 +860,7 @@ class Ref(Field): | ||||||
|         '''This method unlinks p_value (which can be a list of objects) from |         '''This method unlinks p_value (which can be a list of objects) from | ||||||
|            p_obj through this Ref field.''' |            p_obj through this Ref field.''' | ||||||
|         # Security check |         # Security check | ||||||
|         if not noSecurity: obj.allows(self.writePermission, raiseError=True) |         if not noSecurity: obj.mayEdit(self.writePermission, raiseError=True) | ||||||
|         # p_value can be a list of objects |         # p_value can be a list of objects | ||||||
|         if type(value) in sutils.sequenceTypes: |         if type(value) in sutils.sequenceTypes: | ||||||
|             for v in value: self.unlinkObject(obj, v, back=back) |             for v in value: self.unlinkObject(obj, v, back=back) | ||||||
|  | @ -916,8 +911,12 @@ class Ref(Field): | ||||||
|         if objects: |         if objects: | ||||||
|             self.linkObject(obj, objects) |             self.linkObject(obj, objects) | ||||||
| 
 | 
 | ||||||
|     def mayAdd(self, obj): |     def mayAdd(self, obj, checkMayEdit=True): | ||||||
|         '''May the user create a new referred object from p_obj via this Ref?''' |         '''May the user create a new referred object from p_obj via this Ref? | ||||||
|  |            If p_checkMayEdit is False, it means that the condition of being | ||||||
|  |            allowed to edit this Ref field has already been checked somewhere | ||||||
|  |            else (it is always required, we just want to avoid checking it | ||||||
|  |            twice).''' | ||||||
|         # We can't (yet) do that on back references. |         # We can't (yet) do that on back references. | ||||||
|         if self.isBack: return gutils.No('is_back') |         if self.isBack: return gutils.No('is_back') | ||||||
|         # Check if this Ref is addable |         # Check if this Ref is addable | ||||||
|  | @ -931,8 +930,9 @@ class Ref(Field): | ||||||
|             refCount = len(getattr(obj, self.name, ())) |             refCount = len(getattr(obj, self.name, ())) | ||||||
|             if refCount >= self.multiplicity[1]: return gutils.No('max_reached') |             if refCount >= self.multiplicity[1]: return gutils.No('max_reached') | ||||||
|         # May the user edit this Ref field? |         # May the user edit this Ref field? | ||||||
|         if not obj.allows(self.writePermission): |         if checkMayEdit: | ||||||
|             return gutils.No('no_write_perm') |             if not obj.mayEdit(self.writePermission): | ||||||
|  |                 return gutils.No('no_write_perm') | ||||||
|         # May the user create instances of the referred class? |         # May the user create instances of the referred class? | ||||||
|         if not obj.getTool().userMayCreate(self.klass): |         if not obj.getTool().userMayCreate(self.klass): | ||||||
|             return gutils.No('no_add_perm') |             return gutils.No('no_add_perm') | ||||||
|  | @ -943,9 +943,8 @@ class Ref(Field): | ||||||
|            m_mayAdd returns False.''' |            m_mayAdd returns False.''' | ||||||
|         may = self.mayAdd(obj) |         may = self.mayAdd(obj) | ||||||
|         if not may: |         if not may: | ||||||
|             from AccessControl import Unauthorized |             obj.raiseUnauthorized("User can't write Ref field '%s' (%s)." % \ | ||||||
|             raise Unauthorized("User can't write Ref field '%s' (%s)." % \ |                                   (self.name, may.msg)) | ||||||
|                                (self.name, may.msg)) |  | ||||||
| 
 | 
 | ||||||
|     def getCbJsInit(self, obj): |     def getCbJsInit(self, obj): | ||||||
|         '''When checkboxes are enabled, this method defines a JS associative |         '''When checkboxes are enabled, this method defines a JS associative | ||||||
|  | @ -1070,7 +1069,7 @@ class Ref(Field): | ||||||
|             # "link_many", "unlink_many", "delete_many". As a preamble, perform |             # "link_many", "unlink_many", "delete_many". As a preamble, perform | ||||||
|             # a security check once, instead of doing it on every object-level |             # a security check once, instead of doing it on every object-level | ||||||
|             # operation. |             # operation. | ||||||
|             obj.allows(self.writePermission, raiseError=True) |             obj.mayEdit(self.writePermission, raiseError=True) | ||||||
|             # Get the (un-)checked objects from the request. |             # Get the (un-)checked objects from the request. | ||||||
|             uids = rq['targetUid'].strip(',') or (); |             uids = rq['targetUid'].strip(',') or (); | ||||||
|             if uids: uids = uids.split(',') |             if uids: uids = uids.split(',') | ||||||
|  |  | ||||||
|  | @ -397,7 +397,7 @@ class Transition: | ||||||
|         if not obj.isTemporary(): obj.reindex() |         if not obj.isTemporary(): obj.reindex() | ||||||
|         # If we are viewing the object and if the logged user looses the |         # If we are viewing the object and if the logged user looses the | ||||||
|         # permission to view it, redirect the user to its home page. |         # permission to view it, redirect the user to its home page. | ||||||
|         if not obj.allows('read') and \ |         if not obj.mayView() and \ | ||||||
|            (obj.absolute_url_path() in rq['HTTP_REFERER']): |            (obj.absolute_url_path() in rq['HTTP_REFERER']): | ||||||
|             back = tool.getHomePage() |             back = tool.getHomePage() | ||||||
|         else: |         else: | ||||||
|  |  | ||||||
|  | @ -276,8 +276,7 @@ class BaseMixin: | ||||||
|         # used back/forward buttons of its browser... |         # used back/forward buttons of its browser... | ||||||
|         userId = user.login |         userId = user.login | ||||||
|         if (page in self.locks) and (userId != self.locks[page][0]): |         if (page in self.locks) and (userId != self.locks[page][0]): | ||||||
|             from AccessControl import Unauthorized |             self.raiseUnauthorized('This page is locked.') | ||||||
|             raise Unauthorized('This page is locked.') |  | ||||||
|         # Set the lock |         # Set the lock | ||||||
|         from DateTime import DateTime |         from DateTime import DateTime | ||||||
|         self.locks[page] = (userId, DateTime()) |         self.locks[page] = (userId, DateTime()) | ||||||
|  | @ -301,8 +300,7 @@ class BaseMixin: | ||||||
|         if not force: |         if not force: | ||||||
|             userId = self.getTool().getUser().login |             userId = self.getTool().getUser().login | ||||||
|             if self.locks[page][0] != userId: |             if self.locks[page][0] != userId: | ||||||
|                 from AccessControl import Unauthorized |                 self.raiseUnauthorized('This page was locked by someone else.') | ||||||
|                 raise Unauthorized('This page was locked by someone else.') |  | ||||||
|         # Remove the lock |         # Remove the lock | ||||||
|         del self.locks[page] |         del self.locks[page] | ||||||
| 
 | 
 | ||||||
|  | @ -444,10 +442,9 @@ class BaseMixin: | ||||||
|         # onEdit), redirect to the main site page. |         # onEdit), redirect to the main site page. | ||||||
|         if not getattr(obj.getParentNode().aq_base, obj.id, None): |         if not getattr(obj.getParentNode().aq_base, obj.id, None): | ||||||
|             return self.goto(tool.getSiteUrl(), msg) |             return self.goto(tool.getSiteUrl(), msg) | ||||||
|         # If the user can't access the object anymore, redirect him to the |         # If the user can't access the object anymore, redirect him to its home | ||||||
|         # main site page. |         # page. | ||||||
|         if not obj.allows('read'): |         if not obj.mayView(): return self.goto(tool.getHomePage(), msg) | ||||||
|             return self.goto(tool.getSiteUrl(), msg) |  | ||||||
|         if (buttonClicked == 'save') or saveConfirmed: |         if (buttonClicked == 'save') or saveConfirmed: | ||||||
|             obj.say(msg) |             obj.say(msg) | ||||||
|             if isNew and initiator: |             if isNew and initiator: | ||||||
|  | @ -520,8 +517,7 @@ class BaseMixin: | ||||||
|            corresponding Appy wrapper and returns, as XML, its result.''' |            corresponding Appy wrapper and returns, as XML, its result.''' | ||||||
|         self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8') |         self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8') | ||||||
|         # Check if the user is allowed to consult this object |         # Check if the user is allowed to consult this object | ||||||
|         if not self.allows('read'): |         if not self.mayView(): return XmlMarshaller().marshall('Unauthorized') | ||||||
|             return XmlMarshaller().marshall('Unauthorized') |  | ||||||
|         if not action: |         if not action: | ||||||
|             marshaller = XmlMarshaller(rootTag=self.getClass().__name__, |             marshaller = XmlMarshaller(rootTag=self.getClass().__name__, | ||||||
|                                        dumpUnicode=True) |                                        dumpUnicode=True) | ||||||
|  | @ -576,6 +572,12 @@ class BaseMixin: | ||||||
|         if rq.get('appy', None) == '1': obj = obj.appy() |         if rq.get('appy', None) == '1': obj = obj.appy() | ||||||
|         return getattr(obj, 'on'+action)() |         return getattr(obj, 'on'+action)() | ||||||
| 
 | 
 | ||||||
|  |     def raiseUnauthorized(self, msg=None): | ||||||
|  |         '''Raise an error "Unauthorized access".''' | ||||||
|  |         from AccessControl import Unauthorized | ||||||
|  |         if msg: raise Unauthorized(msg) | ||||||
|  |         raise Unauthorized() | ||||||
|  | 
 | ||||||
|     def rememberPreviousData(self, fields): |     def rememberPreviousData(self, fields): | ||||||
|         '''This method is called before updating an object and remembers, for |         '''This method is called before updating an object and remembers, for | ||||||
|            every historized field, the previous value. Result is a dict |            every historized field, the previous value. Result is a dict | ||||||
|  | @ -1131,10 +1133,11 @@ class BaseMixin: | ||||||
|         return 'main' |         return 'main' | ||||||
| 
 | 
 | ||||||
|     def mayAct(self): |     def mayAct(self): | ||||||
|         '''May the currently logged user see column "actions" for this |         '''m_mayAct allows to hide the whole set of actions for an object. | ||||||
|            object? This can be used for hiding the "edit" icon, for example: |            Indeed, beyond workflow security, it can be useful to hide controls | ||||||
|            when a user may edit only a restricted set of fields on an object, |            like "edit" icons/buttons. For example, if a user may only edit some | ||||||
|            we may avoid showing him the global "edit" icon.''' |            Ref fields with add=True on an object, when clicking on "edit", he | ||||||
|  |            will see an empty edit form.''' | ||||||
|         appyObj = self.appy() |         appyObj = self.appy() | ||||||
|         if hasattr(appyObj, 'mayAct'): return appyObj.mayAct() |         if hasattr(appyObj, 'mayAct'): return appyObj.mayAct() | ||||||
|         return True |         return True | ||||||
|  | @ -1148,14 +1151,34 @@ class BaseMixin: | ||||||
|         if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete() |         if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete() | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|     def mayEdit(self, permission='write'): |     def mayEdit(self, permission='write', permOnly=False, raiseError=False): | ||||||
|         '''May the currently logged user edit this object? p_perm can be a |         '''May the currently logged user edit this object? p_permission can be a | ||||||
|            field-specific permission.''' |            field-specific permission. If p_permOnly is True, the specific | ||||||
|         res = self.allows(permission) |            user-defined condition is not evaluated. If p_raiseError is True, if | ||||||
|  |            the user may not edit p_self, an error is raised.''' | ||||||
|  |         res = self.allows(permission, raiseError=raiseError) | ||||||
|  |         if not res: return | ||||||
|  |         if permOnly: return res | ||||||
|  |         # An additional, user-defined condition, may refine the base permission. | ||||||
|  |         appyObj = self.appy() | ||||||
|  |         if hasattr(appyObj, 'mayEdit'): | ||||||
|  |             res = appyObj.mayEdit() | ||||||
|  |             if not res and raiseError: self.raiseUnauthorized() | ||||||
|  |             return res | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def mayView(self, permission='read', raiseError=False): | ||||||
|  |         '''May the currently logged user view this object? p_permission can be a | ||||||
|  |            field-specific permission. If p_raiseError is True, if the user may | ||||||
|  |            not view p_self, an error is raised.''' | ||||||
|  |         res = self.allows(permission, raiseError=raiseError) | ||||||
|         if not res: return |         if not res: return | ||||||
|         # An additional, user-defined condition, may refine the base permission. |         # An additional, user-defined condition, may refine the base permission. | ||||||
|         appyObj = self.appy() |         appyObj = self.appy() | ||||||
|         if hasattr(appyObj, 'mayEdit'): return appyObj.mayEdit() |         if hasattr(appyObj, 'mayView'): | ||||||
|  |             res = appyObj.mayView() | ||||||
|  |             if not res and raiseError: self.raiseUnauthorized() | ||||||
|  |             return res | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|     def onExecuteAction(self): |     def onExecuteAction(self): | ||||||
|  | @ -1512,14 +1535,12 @@ class BaseMixin: | ||||||
|            is retrieved from the request.''' |            is retrieved from the request.''' | ||||||
|         name = self.REQUEST.get('name') |         name = self.REQUEST.get('name') | ||||||
|         if not name: return |         if not name: return | ||||||
|  |         # Security check | ||||||
|         if '_img_' not in name: |         if '_img_' not in name: | ||||||
|             appyType = self.getAppyType(name) |             field = self.getAppyType(name) | ||||||
|         else: |         else: | ||||||
|             appyType = self.getAppyType(name.split('_img_')[0]) |             field = self.getAppyType(name.split('_img_')[0]) | ||||||
|         if (not appyType.isShowable(self, 'view')) and \ |         self.mayView(field.readPermission, raiseError=True) | ||||||
|            (not appyType.isShowable(self, 'result')): |  | ||||||
|             from zExceptions import NotFound |  | ||||||
|             raise NotFound() |  | ||||||
|         info = getattr(self.aq_base, name, None) |         info = getattr(self.aq_base, name, None) | ||||||
|         if info: |         if info: | ||||||
|             # Write the file in the HTTP response. |             # Write the file in the HTTP response. | ||||||
|  | @ -1556,16 +1577,13 @@ class BaseMixin: | ||||||
|     def allows(self, permission, raiseError=False): |     def allows(self, permission, raiseError=False): | ||||||
|         '''Has the logged user p_permission on p_self ?''' |         '''Has the logged user p_permission on p_self ?''' | ||||||
|         res = self.getTool().getUser().has_permission(permission, self) |         res = self.getTool().getUser().has_permission(permission, self) | ||||||
|         if not res and raiseError: |         if not res and raiseError: self.raiseUnauthorized() | ||||||
|             from AccessControl import Unauthorized |  | ||||||
|             raise Unauthorized |  | ||||||
|         return res |         return res | ||||||
| 
 | 
 | ||||||
|     def isTemporary(self): |     def isTemporary(self): | ||||||
|         '''Is this object temporary ?''' |         '''Is this object temporary ?''' | ||||||
|         parent = self.getParentNode() |         parent = self.getParentNode() | ||||||
|         if not parent: # Is propably being created through code |         if not parent: return # Is probably being created through code | ||||||
|             return False |  | ||||||
|         return parent.getId() == 'temp_folder' |         return parent.getId() == 'temp_folder' | ||||||
| 
 | 
 | ||||||
|     def onProcess(self): |     def onProcess(self): | ||||||
|  | @ -1575,7 +1593,7 @@ class BaseMixin: | ||||||
| 
 | 
 | ||||||
|     def onCall(self): |     def onCall(self): | ||||||
|         '''Calls a specific method on the corresponding wrapper.''' |         '''Calls a specific method on the corresponding wrapper.''' | ||||||
|         self.allows('read', raiseError=True) |         self.mayView(raiseError=True) | ||||||
|         method = self.REQUEST['method'] |         method = self.REQUEST['method'] | ||||||
|         obj = self.appy() |         obj = self.appy() | ||||||
|         return getattr(obj, method)() |         return getattr(obj, method)() | ||||||
|  |  | ||||||
|  | @ -104,7 +104,7 @@ td.search { padding-top: 8px } | ||||||
| .dropdown a:hover { text-decoration: underline } | .dropdown a:hover { text-decoration: underline } | ||||||
| .list { margin-bottom: 3px } | .list { margin-bottom: 3px } | ||||||
| .list td, .list th { border: 3px solid #ededed; color: grey; | .list td, .list th { border: 3px solid #ededed; color: grey; | ||||||
|                      padding: 3px 5px 0 5px } |                      padding: 3px 5px 3px 5px } | ||||||
| .list th { background-color: #e5e5e5; font-style: italic; font-weight: normal } | .list th { background-color: #e5e5e5; font-style: italic; font-weight: normal } | ||||||
| .grid th { font-style: italic; font-weight: normal; | .grid th { font-style: italic; font-weight: normal; | ||||||
|            border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px } |            border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px } | ||||||
|  |  | ||||||
|  | @ -302,43 +302,49 @@ class ToolWrapper(AbstractWrapper): | ||||||
|     # Show on query list or grid, the field content for a given object. |     # Show on query list or grid, the field content for a given object. | ||||||
|     pxQueryField = Px(''' |     pxQueryField = Px(''' | ||||||
|      <!-- Title --> |      <!-- Title --> | ||||||
|      <x if="field.name == 'title'" |      <x if="field.name == 'title'"> | ||||||
|         var2="navInfo='search.%s.%s.%d.%d' % \ |       <x if="mayView" | ||||||
|  |          var2="navInfo='search.%s.%s.%d.%d' % \ | ||||||
|                 (className, searchName, startNumber+currentNumber, totalNumber); |                 (className, searchName, startNumber+currentNumber, totalNumber); | ||||||
|               cssClass=zobj.getCssFor('title')"> |                cssClass=zobj.getCssFor('title')"> | ||||||
|       <x>::zobj.getSupTitle(navInfo)</x> |        <x>::zobj.getSupTitle(navInfo)</x> | ||||||
|       <a href=":zobj.getUrl(nav=navInfo, page=zobj.getDefaultViewPage())" |        <a href=":zobj.getUrl(nav=navInfo, page=zobj.getDefaultViewPage())" | ||||||
|          if="enableLinks" class=":cssClass">:zobj.Title()</a><span |           if="enableLinks" class=":cssClass">:zobj.Title()</a><span | ||||||
|          if="not enableLinks" class=":cssClass">:zobj.Title()</span><span |           if="not enableLinks" class=":cssClass">:zobj.Title()</span><span | ||||||
|          style=":showSubTitles and 'display:inline' or 'display:none'" |           style=":showSubTitles and 'display:inline' or 'display:none'" | ||||||
|          name="subTitle">::zobj.getSubTitle()</span> |           name="subTitle">::zobj.getSubTitle()</span> | ||||||
| 
 | 
 | ||||||
|       <!-- Actions --> |        <!-- Actions --> | ||||||
|       <table class="noStyle" if="zobj.mayAct()"> |        <table class="noStyle" if="zobj.mayAct()"> | ||||||
|        <tr> |         <tr> | ||||||
|         <!-- Edit --> |          <!-- Edit --> | ||||||
|         <td if="zobj.mayEdit()"> |          <td if="zobj.mayEdit()"> | ||||||
|          <a var="navInfo='search.%s.%s.%d.%d' % \ |           <a var="navInfo='search.%s.%s.%d.%d' % \ | ||||||
|                (className, searchName, loop.zobj.nb+1+startNumber, totalNumber)" |                (className, searchName, loop.zobj.nb+1+startNumber, totalNumber)" | ||||||
|             href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \ |              href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \ | ||||||
|                                nav=navInfo)"> |                                 nav=navInfo)"> | ||||||
|          <img src=":url('edit')" title=":_('object_edit')"/></a> |           <img src=":url('edit')" title=":_('object_edit')"/></a> | ||||||
|         </td> |          </td> | ||||||
|         <td> |          <td> | ||||||
|          <!-- Delete --> |           <!-- Delete --> | ||||||
|          <img if="zobj.mayDelete()" class="clickable" src=":url('delete')" |           <img if="zobj.mayDelete()" class="clickable" src=":url('delete')" | ||||||
|               title=":_('object_delete')" |                title=":_('object_delete')" | ||||||
|               onClick=":'onDeleteObject(%s)' % q(zobj.UID())"/> |                onClick=":'onDeleteObject(%s)' % q(zobj.id)"/> | ||||||
|         </td> |          </td> | ||||||
|         <!-- Workflow transitions --> |          <!-- Workflow transitions --> | ||||||
|         <td if="zobj.showTransitions('result')" |          <td if="zobj.showTransitions('result')" | ||||||
|             var2="targetObj=zobj; |              var2="targetObj=zobj; | ||||||
|                   buttonsMode='small'">:targetObj.appy().pxTransitions</td> |                    buttonsMode='small'">:targetObj.appy().pxTransitions</td> | ||||||
|        </tr> |         </tr> | ||||||
|       </table> |        </table> | ||||||
|  |       </x> | ||||||
|  |       <x if="not mayView"> | ||||||
|  |        <img src=":url('fake')" style="margin-right: 5px"/> | ||||||
|  |        <x>:_('unauthorized')</x> | ||||||
|  |       </x> | ||||||
|      </x> |      </x> | ||||||
|      <!-- Any other field --> |      <!-- Any other field --> | ||||||
|      <x if="field.name != 'title'"> |      <x if="(field.name != 'title') and mayView"> | ||||||
|       <x var="layoutType='cell'; innerRef=True" |       <x var="layoutType='cell'; innerRef=True" | ||||||
|          if="field.isShowable(zobj, 'result')">:field.pxRender</x> |          if="field.isShowable(zobj, 'result')">:field.pxRender</x> | ||||||
|      </x>''') |      </x>''') | ||||||
|  | @ -361,7 +367,7 @@ class ToolWrapper(AbstractWrapper): | ||||||
|       <!-- Results --> |       <!-- Results --> | ||||||
|       <tr for="zobj in zobjects" id="query_row" valign="top" |       <tr for="zobj in zobjects" id="query_row" valign="top" | ||||||
|           var2="currentNumber=currentNumber + 1; |           var2="currentNumber=currentNumber + 1; | ||||||
|                 obj=zobj.appy()" |                 obj=zobj.appy(); mayView=zobj.mayView()" | ||||||
|           class=":loop.zobj.odd and 'even' or 'odd'"> |           class=":loop.zobj.odd and 'even' or 'odd'"> | ||||||
|         <td for="column in columns" |         <td for="column in columns" | ||||||
|             var2="field=column.field" id=":'field_%s' % field.name" |             var2="field=column.field" id=":'field_%s' % field.name" | ||||||
|  | @ -378,7 +384,8 @@ class ToolWrapper(AbstractWrapper): | ||||||
|                  rows=ztool.splitList(zobjects, cols)"> |                  rows=ztool.splitList(zobjects, cols)"> | ||||||
|       <tr for="row in rows" valign="middle"> |       <tr for="row in rows" valign="middle"> | ||||||
|        <td for="zobj in row" width=":'%d%%' % (100/cols)" align="center" |        <td for="zobj in row" width=":'%d%%' % (100/cols)" align="center" | ||||||
|            style="padding-top: 25px" var2="obj=zobj.appy()"> |            style="padding-top: 25px" | ||||||
|  |            var2="obj=zobj.appy(); mayView=zobj.mayView()"> | ||||||
|         <x var="currentNumber=currentNumber + 1" |         <x var="currentNumber=currentNumber + 1" | ||||||
|            for="column in columns" |            for="column in columns" | ||||||
|            var2="field=column.field">:tool.pxQueryField</x> |            var2="field=column.field">:tool.pxQueryField</x> | ||||||
|  |  | ||||||
|  | @ -454,6 +454,7 @@ class AbstractWrapper(object): | ||||||
|             var="previousPage=phaseObj.getPreviousPage(page)[0]; |             var="previousPage=phaseObj.getPreviousPage(page)[0]; | ||||||
|                  nextPage=phaseObj.getNextPage(page)[0]; |                  nextPage=phaseObj.getNextPage(page)[0]; | ||||||
|                  isEdit=layoutType == 'edit'; |                  isEdit=layoutType == 'edit'; | ||||||
|  |                  mayAct=not isEdit and zobj.mayAct(); | ||||||
|                  pageInfo=phaseObj.pagesInfo[page]"> |                  pageInfo=phaseObj.pagesInfo[page]"> | ||||||
|       <tr valign="top"> |       <tr valign="top"> | ||||||
|        <!-- Refresh --> |        <!-- Refresh --> | ||||||
|  | @ -486,7 +487,6 @@ class AbstractWrapper(object): | ||||||
|                style=":'%s; %s' % (url('save', bg=True), \ |                style=":'%s; %s' % (url('save', bg=True), \ | ||||||
|                                    ztool.getButtonWidth(label))" /> |                                    ztool.getButtonWidth(label))" /> | ||||||
|        </td> |        </td> | ||||||
| 
 |  | ||||||
|        <!-- Cancel --> |        <!-- Cancel --> | ||||||
|        <td if="isEdit and pageInfo.showCancel"> |        <td if="isEdit and pageInfo.showCancel"> | ||||||
|         <input type="button" class="button" onClick="submitAppyForm('cancel')" |         <input type="button" class="button" onClick="submitAppyForm('cancel')" | ||||||
|  | @ -494,11 +494,10 @@ class AbstractWrapper(object): | ||||||
|                style=":'%s; %s' % (url('cancel', bg=True), \ |                style=":'%s; %s' % (url('cancel', bg=True), \ | ||||||
|                                    ztool.getButtonWidth(label))"/> |                                    ztool.getButtonWidth(label))"/> | ||||||
|        </td> |        </td> | ||||||
| 
 |  | ||||||
|        <td if="not isEdit" |        <td if="not isEdit" | ||||||
|            var2="locked=zobj.isLocked(user, page); |            var2="locked=zobj.isLocked(user, page); | ||||||
|                  editable=pageInfo.showOnEdit and pageInfo.showEdit and \ |                  editable=pageInfo.showOnEdit and pageInfo.showEdit and \ | ||||||
|                           zobj.mayEdit()"> |                           mayAct and zobj.mayEdit()"> | ||||||
| 
 | 
 | ||||||
|         <!-- Edit --> |         <!-- Edit --> | ||||||
|         <input type="button" class="button" if="editable and not locked" |         <input type="button" class="button" if="editable and not locked" | ||||||
|  | @ -540,7 +539,8 @@ class AbstractWrapper(object): | ||||||
| 
 | 
 | ||||||
|        <!-- Workflow transitions --> |        <!-- Workflow transitions --> | ||||||
|        <td var="targetObj=zobj; buttonsMode='normal'" |        <td var="targetObj=zobj; buttonsMode='normal'" | ||||||
|            if="targetObj.showTransitions(layoutType)">:obj.pxTransitions</td> |            if="mayAct and \ | ||||||
|  |                targetObj.showTransitions(layoutType)">:obj.pxTransitions</td> | ||||||
|       </tr> |       </tr> | ||||||
|      </table>''') |      </table>''') | ||||||
| 
 | 
 | ||||||
|  | @ -554,7 +554,7 @@ class AbstractWrapper(object): | ||||||
|      </table>''') |      </table>''') | ||||||
| 
 | 
 | ||||||
|     pxView = Px(''' |     pxView = Px(''' | ||||||
|      <x var="x=zobj.allows('read', raiseError=True); |      <x var="x=zobj.mayView(raiseError=True); | ||||||
|              errors=req.get('errors', {}); |              errors=req.get('errors', {}); | ||||||
|              layout=zobj.getPageLayout(layoutType); |              layout=zobj.getPageLayout(layoutType); | ||||||
|              phaseObj=zobj.getAppyPhases(currentOnly=True, layoutType='view'); |              phaseObj=zobj.getAppyPhases(currentOnly=True, layoutType='view'); | ||||||
|  | @ -570,7 +570,7 @@ class AbstractWrapper(object): | ||||||
|      </x>''', template=pxTemplate, hook='content') |      </x>''', template=pxTemplate, hook='content') | ||||||
| 
 | 
 | ||||||
|     pxEdit = Px(''' |     pxEdit = Px(''' | ||||||
|      <x var="x=zobj.allows('write', raiseError=True); |      <x var="x=zobj.mayEdit(raiseError=True, permOnly=zobj.isTemporary()); | ||||||
|              errors=req.get('errors', {}); |              errors=req.get('errors', {}); | ||||||
|              layout=zobj.getPageLayout(layoutType); |              layout=zobj.getPageLayout(layoutType); | ||||||
|              cssJs={}; |              cssJs={}; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay