[gen] Display action fields with layout 'buttons' in query results and tied objects for refs, besides transitions. [gen] Show delete button when allowed on the view layout. [gen] Improved method AbstractWrapper::createFrom.

This commit is contained in:
Gaetan Delannay 2015-01-06 21:13:30 +01:00
parent 225ea927a4
commit 746a6cd52d
7 changed files with 171 additions and 131 deletions

View file

@ -30,21 +30,23 @@ class Action(Field):
<form var="formId='%s_%s_form' % (zobj.id, name); <form var="formId='%s_%s_form' % (zobj.id, name);
label=_(field.labelId); label=_(field.labelId);
descr=field.hasDescr and _(field.descrId) or None; descr=field.hasDescr and _(field.descrId) or None;
buttonWidth=ztool.getButtonWidth(label)" buttonWidth=ztool.getButtonWidth(label);
smallButtons=smallButtons|False;
css=smallButtons and 'buttonSmall button' or 'button'"
id=":formId" action=":ztool.absolute_url() + '/do'" id=":formId" action=":ztool.absolute_url() + '/do'"
style="display:inline"> style="display:inline">
<input type="hidden" name="action" value="ExecuteAction"/> <input type="hidden" name="action" value="ExecuteAction"/>
<input type="hidden" name="objectUid" value=":zobj.id"/> <input type="hidden" name="objectUid" value=":zobj.id"/>
<input type="hidden" name="fieldName" value=":name"/> <input type="hidden" name="fieldName" value=":name"/>
<input type="hidden" name="comment" value=""/> <input type="hidden" name="comment" value=""/>
<input if="field.confirm" type="button" class="button" title=":descr" <input if="field.confirm" type="button" class=":css" title=":descr"
var="labelConfirm=_(field.labelId + '_confirm'); var="labelConfirm=_(field.labelId + '_confirm');
commentParam=(field.confirm == 'text') and 'true' or 'false'" commentParam=(field.confirm == 'text') and 'true' or 'false'"
value=":label" value=":label"
style=":'%s; %s' % (url(field.icon, bg=True), buttonWidth)" style=":'%s; %s' % (url(field.icon, bg=True), buttonWidth)"
onclick=":'askConfirm(%s,%s,%s,%s)' % (q('form'), q(formId), \ onclick=":'askConfirm(%s,%s,%s,%s)' % (q('form'), q(formId), \
q(labelConfirm), commentParam)"/> q(labelConfirm), commentParam)"/>
<input if="not field.confirm" type="submit" class="button" name="do" <input if="not field.confirm" type="submit" class=":css" name="do"
value=":label" title=":descr" value=":label" title=":descr"
style=":'%s; %s' % (url(field.icon, bg=True), buttonWidth)"/> style=":'%s; %s' % (url(field.icon, bg=True), buttonWidth)"/>
</form>''') </form>''')

View file

@ -86,10 +86,10 @@ class FileInfo:
self.uploadName = uploadName # The name of the uploaded file self.uploadName = uploadName # The name of the uploaded file
self.size = 0 # Its size, in bytes self.size = 0 # Its size, in bytes
self.mimeType = None # Its MIME type self.mimeType = None # Its MIME type
self.modified = None # The last modification date for this file. self.modified = None # The last modification date for this file
# Complete metadata if p_inDb is False # Complete metadata if p_inDb is False
if not inDb: if not inDb:
self.fsName = '' # Already included in self.fsPath. self.fsName = '' # Already included in self.fsPath
# We will not store p_inDb. Checking if self.fsName is the empty # We will not store p_inDb. Checking if self.fsName is the empty
# string is equivalent. # string is equivalent.
fileInfo = os.stat(self.fsPath) fileInfo = os.stat(self.fsPath)
@ -166,20 +166,25 @@ class FileInfo:
'''Writes to the filesystem the p_fileObj file, that can be: '''Writes to the filesystem the p_fileObj file, that can be:
- a Zope FileUpload (coming from a HTTP post); - a Zope FileUpload (coming from a HTTP post);
- a OFS.Image.File object (legacy within-ZODB file object); - a OFS.Image.File object (legacy within-ZODB file object);
- another ("not-in-DB") FileInfo instance;
- a tuple (fileName, fileContent, mimeType) - a tuple (fileName, fileContent, mimeType)
(see doc in method File.store below).''' (see doc in method File.store below).'''
# Determine p_fileObj's type # Determine p_fileObj's type
fileType = fileObj.__class__.__name__ fileType = fileObj.__class__.__name__
# Set MIME type # Determine the MIME type and the base name of the file to store
if fileType == 'FileUpload': if fileType == 'FileUpload':
mimeType = self.getMimeTypeFromFileUpload(fileObj) mimeType = self.getMimeTypeFromFileUpload(fileObj)
fileName = fileObj.filename
elif fileType == 'File': elif fileType == 'File':
mimeType = fileObj.content_type mimeType = fileObj.content_type
fileName = fileObj.filename
elif fileType == 'FileInfo':
mimeType = fileObj.mimeType
fileName = fileObj.uploadName
else: else:
mimeType = fileObj[2] mimeType = fileObj[2]
fileName = fileObj[0]
self.mimeType = mimeType or File.defaultMimeType self.mimeType = mimeType or File.defaultMimeType
# Determine the original name of the file to store.
fileName= fileType.startswith('File') and fileObj.filename or fileObj[0]
if not fileName: if not fileName:
# Name it according to field name. Deduce file extension from the # Name it according to field name. Deduce file extension from the
# MIME type. # MIME type.
@ -195,12 +200,12 @@ class FileInfo:
fsName = osPathJoin(dbFolder, self.fsPath, self.fsName) fsName = osPathJoin(dbFolder, self.fsPath, self.fsName)
f = file(fsName, 'wb') f = file(fsName, 'wb')
if fileType == 'FileUpload': if fileType == 'FileUpload':
# Write the FileUpload instance on disk. # Write the FileUpload instance on disk
self.size = self.replicateFile(fileObj, f) self.size = self.replicateFile(fileObj, f)
elif fileType == 'File': elif fileType == 'File':
# Write the File instance on disk. # Write the File instance on disk
if fileObj.data.__class__.__name__ == 'Pdata': if fileObj.data.__class__.__name__ == 'Pdata':
# The file content is splitted in several chunks. # The file content is splitted in several chunks
f.write(fileObj.data.data) f.write(fileObj.data.data)
nextPart = fileObj.data.next nextPart = fileObj.data.next
while nextPart: while nextPart:
@ -210,10 +215,14 @@ class FileInfo:
# Only one chunk # Only one chunk
f.write(fileObj.data) f.write(fileObj.data)
self.size = fileObj.size self.size = fileObj.size
elif fileType == 'FileInfo':
src = file(fileObj.fsPath, 'rb')
self.size = self.replicateFile(src, f)
src.close()
else: else:
# Write fileObj[1] on disk. # Write fileObj[1] on disk
if fileObj[1].__class__.__name__ == 'file': if fileObj[1].__class__.__name__ == 'file':
# It is an open file handler. # It is an open file handler
self.size = self.replicateFile(fileObj[1], f) self.size = self.replicateFile(fileObj[1], f)
else: else:
# We have file content directly in fileObj[1] # We have file content directly in fileObj[1]
@ -361,7 +370,17 @@ class File(Field):
name = requestName or self.name name = requestName or self.name
return obj.REQUEST.get('%s_file' % name) return obj.REQUEST.get('%s_file' % name)
def getCopyValue(self, obj): raise Exception('Not implemented yet.') def getCopyValue(self, obj):
'''Create a copy of the FileInfo instance stored for p_obj for this
field. This copy will contain the absolute path to the file on the
filesystem. This way, the file may be read independently from p_obj
(and copied somewhere else).'''
info = self.getValue(obj)
if not info: return
# Create a "not-in-DB", temporary FileInfo
return FileInfo(info.getFilePath(obj), inDb=False,
uploadName=info.uploadName)
def getDefaultLayouts(self): return {'view':'l-f','edit':'lrv-f'} def getDefaultLayouts(self): return {'view':'l-f','edit':'lrv-f'}
def isEmptyValue(self, obj, value): def isEmptyValue(self, obj, value):
@ -405,13 +424,15 @@ class File(Field):
f. a 3-tuple (fileName, fileContent, mimeType) where f. a 3-tuple (fileName, fileContent, mimeType) where
- fileName and fileContent have the same meaning than above; - fileName and fileContent have the same meaning than above;
- mimeType is the MIME type of the file. - mimeType is the MIME type of the file.
g. a FileInfo instance, that must be "not-in-DB", ie, with an
absolute path in attribute fsPath.
''' '''
zobj = obj.o zobj = obj.o
if value: if value:
# There is a new value to store. Get the folder on disk where to # There is a new value to store. Get the folder on disk where to
# store the new file. # store the new file.
dbFolder, folder = zobj.getFsFolder(create=True) dbFolder, folder = zobj.getFsFolder(create=True)
# Remove the previous file if it existed. # Remove the previous file if it existed
info = getattr(obj.aq_base, self.name, None) info = getattr(obj.aq_base, self.name, None)
if info: if info:
# The previous file can be a legacy File object in an old # The previous file can be a legacy File object in an old
@ -432,6 +453,9 @@ class File(Field):
elif isinstance(value, basestring): elif isinstance(value, basestring):
# Case d # Case d
info.copyFile(self.name, value, dbFolder) info.copyFile(self.name, value, dbFolder)
elif isinstance(value, FileInfo):
# Case g
info.writeFile(self.name, value, dbFolder)
else: else:
# Cases e, f. Extract file name, content and MIME type. # Cases e, f. Extract file name, content and MIME type.
fileName = mimeType = None fileName = mimeType = None
@ -444,7 +468,7 @@ class File(Field):
mimeType = mimeType or guessMimeType(fileName) mimeType = mimeType or guessMimeType(fileName)
info.writeFile(self.name, (fileName, fileContent, mimeType), info.writeFile(self.name, (fileName, fileContent, mimeType),
dbFolder) dbFolder)
# Store the FileInfo instance in the database. # Store the FileInfo instance in the database
setattr(obj, self.name, info) setattr(obj, self.name, info)
else: else:
# I store value "None", excepted if I find in the request the desire # I store value "None", excepted if I find in the request the desire

View file

@ -77,10 +77,9 @@ class Ref(Field):
# This PX displays icons for triggering actions on a given referenced object # This PX displays icons for triggering actions on a given referenced object
# (edit, delete, etc). # (edit, delete, etc).
pxObjectActions = Px(''' pxObjectActions = Px('''
<table class="noStyle"> <div>
<tr>
<!-- Arrows for moving objects up or down --> <!-- Arrows for moving objects up or down -->
<td if="(totalNumber &gt;1) and changeOrder and not inPickList \ <x if="(totalNumber &gt;1) and changeOrder and not inPickList \
and not inMenu" and not inMenu"
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'),
@ -101,41 +100,44 @@ class Ref(Field):
<img if="objectIndex &lt; (totalNumber-1)" class="clickable" <img if="objectIndex &lt; (totalNumber-1)" class="clickable"
src=":url('arrowDown')" title=":_('move_down')" src=":url('arrowDown')" title=":_('move_down')"
onclick=":ajaxBaseCall.replace('**v**', 'down')"/> onclick=":ajaxBaseCall.replace('**v**', 'down')"/>
</td> </x>
<!-- Edit --> <!-- Edit -->
<td if="not field.noForm and tied.o.mayEdit()"> <a if="not field.noForm and tied.o.mayEdit()"
<a var="navInfo=field.getNavInfo(zobj, loop.tied.nb + 1 + startNumber, \ var2="navInfo=field.getNavInfo(zobj, loop.tied.nb + 1 + startNumber, \
totalNumber); totalNumber);
linkInPopup=inPopup or (target.target != '_self')" linkInPopup=inPopup or (target.target != '_self')"
href=":tied.o.getUrl(mode='edit', page='main', nav=navInfo, \ href=":tied.o.getUrl(mode='edit', page='main', nav=navInfo, \
inPopup=linkInPopup)" inPopup=linkInPopup)"
target=":target.target" onclick=":target.openPopup"> target=":target.target" onclick=":target.openPopup">
<img src=":url('edit')" title=":_('object_edit')"/></a> <img src=":url('edit')" title=":_('object_edit')"/>
</td> </a>
<!-- Delete --> <!-- Delete -->
<td if="mayEdit and field.delete and tied.o.mayDelete()"> <img if="mayEdit and field.delete and tied.o.mayDelete()"
<img class="clickable" title=":_('object_delete')" src=":url('delete')" class="clickable" title=":_('object_delete')" src=":url('delete')"
onclick=":'onDeleteObject(%s)' % q(tiedUid)"/> onclick=":'onDeleteObject(%s)' % q(tiedUid)"/>
</td>
<!-- Unlink --> <!-- Unlink -->
<td if="mayUnlink and field.mayUnlinkElement(obj, tied)"> <img if="mayUnlink and field.mayUnlinkElement(obj, tied)"
<img var="imgName=linkList and 'unlinkUp' or 'unlink'; action='unlink'" var2="imgName=linkList and 'unlinkUp' or 'unlink'; action='unlink'"
class="clickable" title=":_('object_unlink')" src=":url(imgName)" class="clickable" title=":_('object_unlink')" src=":url(imgName)"
onclick=":'onLink(%s,%s,%s,%s)' % (q(action), q(zobj.id), \ onclick=":'onLink(%s,%s,%s,%s)' % (q(action), q(zobj.id), \
q(field.name), q(tiedUid))"/> q(field.name), q(tiedUid))"/>
</td>
<!-- Insert (if in pick list) --> <!-- Insert (if in pick list) -->
<td if="inPickList"> <img if="inPickList" var2="action='link'" class="clickable"
<img var="action='link'" class="clickable" title=":_('object_link')" title=":_('object_link')" src=":url(action)"
src=":url(action)"
onclick=":'onLink(%s,%s,%s,%s)' % (q(action), q(zobj.id), \ onclick=":'onLink(%s,%s,%s,%s)' % (q(action), q(zobj.id), \
q(field.name), q(tiedUid))"/> q(field.name), q(tiedUid))"/>
</td>
<!-- Workflow transitions --> <!-- Workflow transitions -->
<td if="tied.o.showTransitions('result')" <x if="tied.o.showTransitions('result')"
var2="targetObj=tied.o; buttonsMode='small'">:tied.pxTransitions</td> var2="targetObj=tied.o; buttonsMode='small'">:tied.pxTransitions</x>
</tr> <!-- Fields (actions) defined with layout "buttons" -->
</table>''') <x if="not inPopup"
var2="fields=tied.o.getAppyTypes('buttons', 'main', type='Action');
layoutType='view'">
<!-- Call pxView and not pxRender to avoid having a table -->
<x for="field in fields"
var2="name=field.name; smallButtons=True">:field.pxView</x>
</x>
</div>''')
# 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.
@ -797,7 +799,7 @@ class Ref(Field):
UIDs, because m_store on the destination object can store tied UIDs, because m_store on the destination object can store tied
objects based on such a list.''' objects based on such a list.'''
res = getattr(obj.aq_base, self.name, ()) res = getattr(obj.aq_base, self.name, ())
# Return a copy: it can be dangerous to give the real database value. # Return a copy: it can be dangerous to give the real database value
if res: return list(res) if res: return list(res)
def getXmlValue(self, obj, value): def getXmlValue(self, obj, value):
@ -830,7 +832,7 @@ class Ref(Field):
paginated = startNumber != None paginated = startNumber != None
isSearch = False isSearch = False
if 'masterValues' in req: if 'masterValues' in req:
# Convert masterValue(s) from id(s) to real object(s). # Convert masterValue(s) from id(s) to real object(s)
masterValues = req['masterValues'].strip() masterValues = req['masterValues'].strip()
if not masterValues: masterValues = None if not masterValues: masterValues = None
else: else:
@ -865,11 +867,11 @@ class Ref(Field):
objects.objects = [o.appy() for o in objects.objects] objects.objects = [o.appy() for o in objects.objects]
else: else:
objects = self.select(obj) objects = self.select(obj)
# Remove already linked objects if required. # Remove already linked objects if required
if removeLinked: if removeLinked:
uids = getattr(obj.o.aq_base, self.name, None) uids = getattr(obj.o.aq_base, self.name, None)
if uids: if uids:
# Browse objects in reverse order and remove linked objects. # Browse objects in reverse order and remove linked objects
if isSearch: objs = objects.objects if isSearch: objs = objects.objects
else: objs = objects else: objs = objects
i = len(objs) - 1 i = len(objs) - 1
@ -884,7 +886,7 @@ class Ref(Field):
if paginated and not isSearch: if paginated and not isSearch:
total = len(objects) total = len(objects)
objects = objects[startNumber:startNumber + self.maxPerPage] objects = objects[startNumber:startNumber + self.maxPerPage]
# Return the result, wrapped in a SomeObjects instance if required. # Return the result, wrapped in a SomeObjects instance if required
if not someObjects: if not someObjects:
if isSearch: return objects.objects if isSearch: return objects.objects
return objects return objects
@ -909,7 +911,7 @@ class Ref(Field):
for tied in objects: for tied in objects:
menuId = self.menuIdMethod(obj, tied) menuId = self.menuIdMethod(obj, tied)
if menuId in menuIds: if menuId in menuIds:
# We have already encountered this menu. # We have already encountered this menu
menuIndex = menuIds[menuId] menuIndex = menuIds[menuId]
res[menuIndex].objects.append(tied) res[menuIndex].objects.append(tied)
else: else:
@ -947,7 +949,7 @@ class Ref(Field):
'''This method returns the index of the first linked object that must be '''This method returns the index of the first linked object that must be
shown, or None if all linked objects must be shown at once (it shown, or None if all linked objects must be shown at once (it
happens when p_render is "menus").''' happens when p_render is "menus").'''
# When using 'menus' render mode, all linked objects must be shown. # When using 'menus' render mode, all linked objects must be shown
if render == 'menus': return if render == 'menus': return
# When using 'list' (=default) render mode, the index of the first # When using 'list' (=default) render mode, the index of the first
# object to show is in the request. # object to show is in the request.

View file

@ -328,7 +328,7 @@ class Transition:
# functions return True. # functions return True.
hasRole = None hasRole = None
for condition in self.condition: for condition in self.condition:
# "Unwrap" role names from Role instances. # "Unwrap" role names from Role instances
if isinstance(condition, Role): condition = condition.name if isinstance(condition, Role): condition = condition.name
if isinstance(condition, basestring): # It is a role if isinstance(condition, basestring): # It is a role
if hasRole == None: if hasRole == None:
@ -337,7 +337,7 @@ class Transition:
hasRole = True hasRole = True
else: # It is a method else: # It is a method
res = condition(wf, obj.appy()) res = condition(wf, obj.appy())
if not res: return res # False or a No instance. if not res: return res # False or a No instance
if hasRole != False: if hasRole != False:
return True return True
@ -345,13 +345,13 @@ class Transition:
'''Executes the action related to this transition.''' '''Executes the action related to this transition.'''
msg = '' msg = ''
obj = obj.appy() obj = obj.appy()
wf = wf.__instance__ # We need the prototypical instance here. wf = wf.__instance__ # We need the prototypical instance here
if type(self.action) in (tuple, list): if type(self.action) in (tuple, list):
# We need to execute a list of actions # We need to execute a list of actions
for act in self.action: for act in self.action:
msgPart = act(wf, obj) msgPart = act(wf, obj)
if msgPart: msg += msgPart if msgPart: msg += msgPart
else: # We execute a single action only. else: # We execute a single action only
msgPart = self.action(wf, obj) msgPart = self.action(wf, obj)
if msgPart: msg += msgPart if msgPart: msg += msgPart
return msg return msg

View file

@ -365,30 +365,34 @@ class ToolWrapper(AbstractWrapper):
if="sub">::zobj.highlight(sub)</span> if="sub">::zobj.highlight(sub)</span>
<!-- Actions --> <!-- Actions -->
<table class="noStyle" if="not inPopup and zobj.mayAct()"> <div if="not inPopup and zobj.mayAct()">
<tr>
<!-- Edit --> <!-- Edit -->
<td if="zobj.mayEdit()"> <a if="zobj.mayEdit()"
<a var="navInfo='search.%s.%s.%d.%d' % \ var2="navInfo='search.%s.%s.%d.%d' % \
(className, searchName, loop.zobj.nb+1+startNumber, totalNumber); (className, searchName, loop.zobj.nb+1+startNumber, totalNumber);
linkInPopup=inPopup or (target.target != '_self')" linkInPopup=inPopup or (target.target != '_self')"
target=":target.target" onclick=":target.openPopup" target=":target.target" onclick=":target.openPopup"
href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \ href=":zobj.getUrl(mode='edit', page=zobj.getDefaultEditPage(), \
nav=navInfo, inPopup=linkInPopup)"> nav=navInfo, inPopup=linkInPopup)">
<img src=":url('edit')" title=":_('object_edit')"/></a> <img src=":url('edit')" title=":_('object_edit')"/>
</td> </a>
<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.id)"/> onClick=":'onDeleteObject(%s)' % q(zobj.id)"/>
</td>
<!-- Workflow transitions --> <!-- Workflow transitions -->
<td if="zobj.showTransitions('result')" <x if="zobj.showTransitions('result')"
var2="targetObj=zobj; var2="targetObj=zobj;
buttonsMode='small'">:targetObj.appy().pxTransitions</td> buttonsMode='small'">:targetObj.appy().pxTransitions</x>
</tr> <!-- Fields (actions) defined with layout "buttons" -->
</table> <x if="not inPopup"
var2="fields=zobj.getAppyTypes('buttons', 'main', type='Action');
layoutType='view'">
<!-- Call pxView and not pxRender to avoid having a table -->
<x for="field in fields"
var2="name=field.name; smallButtons=True">:field.pxView</x>
</x>
</div>
</x> </x>
<x if="not mayView"> <x if="not mayView">
<img src=":url('fake')" style="margin-right: 5px"/> <img src=":url('fake')" style="margin-right: 5px"/>

View file

@ -304,7 +304,7 @@ class UserWrapper(AbstractWrapper):
rq = self.request rq = self.request
if (self.user == self) and hasattr(rq, 'userLogins'): if (self.user == self) and hasattr(rq, 'userLogins'):
return rq.userLogins return rq.userLogins
# Compute it. # Compute it
res = [group.login for group in self.groups] res = [group.login for group in self.groups]
if not groupsOnly: res.append(self.login) if not groupsOnly: res.append(self.login)
return res return res

View file

@ -30,7 +30,7 @@ class AbstractWrapper(object):
'(%s).click()' % q(gotoName)"/><img '(%s).click()' % q(gotoName)"/><img
id=":gotoName" name=":gotoName" id=":gotoName" name=":gotoName"
class="clickable" src=":url('gotoNumber')" title=":label" class="clickable" src=":url('gotoNumber')" title=":label"
onClick=":'gotoTied(%s,%s,this.previousSibling,%s)' % \ onclick=":'gotoTied(%s,%s,this.previousSibling,%s)' % \
(q(sourceUrl), q(field.name), totalNumber)"/></x>''') (q(sourceUrl), q(field.name), totalNumber)"/></x>''')
pxNavigationStrip = Px(''' pxNavigationStrip = Px('''
@ -469,7 +469,7 @@ class AbstractWrapper(object):
<!-- Button on the edit page --> <!-- Button on the edit page -->
<x if="isEdit"> <x if="isEdit">
<input type="button" class="button" value=":label" <input type="button" class="button" value=":label"
onClick="submitAppyForm('previous')" onclick="submitAppyForm('previous')"
style=":'%s; %s' % (url('previous', bg=True), buttonWidth)"/> style=":'%s; %s' % (url('previous', bg=True), buttonWidth)"/>
<input type="hidden" name="previousPage" value=":previousPage"/> <input type="hidden" name="previousPage" value=":previousPage"/>
</x> </x>
@ -479,26 +479,22 @@ class AbstractWrapper(object):
onclick=":'goto(%s)' % q(zobj.getUrl(page=previousPage, \ onclick=":'goto(%s)' % q(zobj.getUrl(page=previousPage, \
inPopup=inPopup))"/> inPopup=inPopup))"/>
</x> </x>
<!-- Save --> <!-- Save -->
<input if="isEdit and pageInfo.showSave" <input if="isEdit and pageInfo.showSave"
type="button" class="button" onClick="submitAppyForm('save')" type="button" class="button" onclick="submitAppyForm('save')"
var2="label=_('object_save')" value=":label" var2="label=_('object_save')" value=":label"
style=":'%s; %s' % (url('save', bg=True), \ style=":'%s; %s' % (url('save', bg=True), \
ztool.getButtonWidth(label))" /> ztool.getButtonWidth(label))" />
<!-- Cancel --> <!-- Cancel -->
<input if="isEdit and pageInfo.showCancel" <input if="isEdit and pageInfo.showCancel"
type="button" class="button" onClick="submitAppyForm('cancel')" type="button" class="button" onclick="submitAppyForm('cancel')"
var2="label=_('object_cancel')" value=":label" var2="label=_('object_cancel')" value=":label"
style=":'%s; %s' % (url('cancel', bg=True), \ style=":'%s; %s' % (url('cancel', bg=True), \
ztool.getButtonWidth(label))"/> ztool.getButtonWidth(label))"/>
<x if="not isEdit" <x 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 \
mayAct 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"
var="label=_('object_edit')" value=":label" var="label=_('object_edit')" value=":label"
@ -506,7 +502,6 @@ class AbstractWrapper(object):
ztool.getButtonWidth(label))" ztool.getButtonWidth(label))"
onclick=":'goto(%s)' % q(zobj.getUrl(mode='edit', page=page, \ onclick=":'goto(%s)' % q(zobj.getUrl(mode='edit', page=page, \
inPopup=inPopup))"/> inPopup=inPopup))"/>
<!-- Locked --> <!-- Locked -->
<a if="editable and locked"> <a if="editable and locked">
<img style="cursor: help" <img style="cursor: help"
@ -520,14 +515,20 @@ class AbstractWrapper(object):
src=":url('unlockBig')" src=":url('unlockBig')"
onclick=":'onUnlockPage(%s,%s)' % (q(zobj.id), q(page))"/></a> onclick=":'onUnlockPage(%s,%s)' % (q(zobj.id), q(page))"/></a>
</x> </x>
<!-- Delete -->
<input if="not isEdit and not inPopup and zobj.mayDelete()"
type="button" class="button"
onclick=":'onDeleteObject(%s)' % q(zobj.id)"
var2="label=_('object_delete')" value=":label"
style=":'%s; %s' % (url('delete', bg=True), \
ztool.getButtonWidth(label))"/>
<!-- Next --> <!-- Next -->
<x if="nextPage and pageInfo.showNext" <x if="nextPage and pageInfo.showNext"
var2="label=_('page_next'); var2="label=_('page_next');
buttonWidth=ztool.getButtonWidth(label)"> buttonWidth=ztool.getButtonWidth(label)">
<!-- Button on the edit page --> <!-- Button on the edit page -->
<x if="isEdit"> <x if="isEdit">
<input type="button" class="button" onClick="submitAppyForm('next')" <input type="button" class="button" onclick="submitAppyForm('next')"
style=":'%s; %s' % (url('next', bg=True), buttonWidth)" style=":'%s; %s' % (url('next', bg=True), buttonWidth)"
value=":label"/> value=":label"/>
<input type="hidden" name="nextPage" value=":nextPage"/> <input type="hidden" name="nextPage" value=":nextPage"/>
@ -538,12 +539,10 @@ class AbstractWrapper(object):
onclick=":'goto(%s)' % q(zobj.getUrl(page=nextPage, \ onclick=":'goto(%s)' % q(zobj.getUrl(page=nextPage, \
inPopup=inPopup))"/> inPopup=inPopup))"/>
</x> </x>
<!-- Workflow transitions --> <!-- Workflow transitions -->
<x var="targetObj=zobj; buttonsMode='normal'" <x var="targetObj=zobj; buttonsMode='normal'"
if="mayAct and \ if="mayAct and \
targetObj.showTransitions(layoutType)">:obj.pxTransitions</x> targetObj.showTransitions(layoutType)">:obj.pxTransitions</x>
<!-- Fields (actions) defined with layout "buttons" --> <!-- Fields (actions) defined with layout "buttons" -->
<x if="layoutType != 'edit'" <x if="layoutType != 'edit'"
var2="fields=zobj.getAppyTypes('buttons', page, type='Action'); var2="fields=zobj.getAppyTypes('buttons', page, type='Action');
@ -929,13 +928,17 @@ class AbstractWrapper(object):
return appyObj return appyObj
def createFrom(self, fieldNameOrClass, other, noSecurity=False, def createFrom(self, fieldNameOrClass, other, noSecurity=False,
executeMethods=True, exclude=()): executeMethods=True, exclude=(), keepBase=False):
'''Similar to m_create above, excepted that we will use another object '''Similar to m_create above, excepted that we will use another object
(p_other) as base for filling in data for the object to create. (p_other) as base for filling in data for the object to create.
p_exclude can list fields (by their names) that will not be p_exclude can list fields (by their names) that will not be copied on
copied on p_other. Note that this method does not perform a deep p_other. If p_keepBase is True, basic attributes will be kept on the
copy: objects linked via Ref fields from p_self will be new object: creator and dates "created" and "modified". Else, the
referenced by the clone, but not themselves copied.''' new object's creator will be the logged user.
Note that this method does not perform a deep copy: objects linked
via Ref fields from p_self will be referenced by the clone, but not
themselves copied.'''
# Get the field values to set from p_other and store it in a dict. # Get the field values to set from p_other and store it in a dict.
# p_other may not be of the same class as p_self. # p_other may not be of the same class as p_self.
params = {} params = {}
@ -944,9 +947,14 @@ class AbstractWrapper(object):
if not field.persist or (field.name in exclude) or \ if not field.persist or (field.name in exclude) or \
((field.type == 'Ref') and field.isBack): continue ((field.type == 'Ref') and field.isBack): continue
params[field.name] = field.getCopyValue(other.o) params[field.name] = field.getCopyValue(other.o)
return self.create(fieldNameOrClass, noSecurity=noSecurity, res = self.create(fieldNameOrClass, noSecurity=noSecurity,
raiseOnWrongAttribute=False, raiseOnWrongAttribute=False,
executeMethods=executeMethods, **params) executeMethods=executeMethods, **params)
# Propagate base attributes if required
if keepBase:
for name in ('creator', 'created', 'modified'):
setattr(res.o, name, getattr(other.o, name))
return res
def freeze(self, fieldName, template=None, format='pdf', noSecurity=True, def freeze(self, fieldName, template=None, format='pdf', noSecurity=True,
freezeOdtOnError=True): freezeOdtOnError=True):