[gen] [gen] Action field: param confirm can be 'text': in this case, the confirm popup contains a textarea whose content is passed as param to the method speficied in param 'action'. Action fields can now be defined in a new layout 'buttons' (show='buttons') in order to appear, on the view layout, besides the extsting series of buttons (edit, next/previous pages...).

This commit is contained in:
Gaetan Delannay 2014-10-22 22:17:26 +02:00
parent 960a4c6a46
commit ccf7e44eef
6 changed files with 157 additions and 114 deletions

View file

@ -350,7 +350,7 @@ class Field:
for r in res: for r in res:
if r == layoutType: return True if r == layoutType: return True
return return
elif res in ('view', 'edit', 'result'): elif res in ('view', 'edit', 'result', 'buttons'):
return res == layoutType return res == layoutType
return bool(res) return bool(res)

View file

@ -29,18 +29,22 @@ class Action(Field):
pxView = pxCell = Px(''' pxView = pxCell = Px('''
<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.descrId and _(field.descrId) or None;
buttonWidth=ztool.getButtonWidth(label)" buttonWidth=ztool.getButtonWidth(label)"
id=":formId" action=":ztool.absolute_url() + '/do'"> id=":formId" action=":ztool.absolute_url() + '/do'">
<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 if="field.confirm" type="button" class="button" <input type="hidden" name="comment" value=""/>
var="labelConfirm=_(field.labelId + '_confirm')" value=":label" <input if="field.confirm" type="button" class="button" title=":descr"
var="labelConfirm=_(field.labelId + '_confirm');
commentParam=(field.confirm == 'text') and 'true' or 'false'"
value=":label"
style=":'%s; %s' % (url('action', bg=True), buttonWidth)" style=":'%s; %s' % (url('action', bg=True), buttonWidth)"
onclick=":'askConfirm(%s,%s,%s)' % (q('form'), q(formId), \ onclick=":'askConfirm(%s,%s,%s,%s)' % (q('form'), q(formId), \
q(labelConfirm))"/> q(labelConfirm), commentParam)"/>
<input if="not field.confirm" type="submit" class="button" name="do" <input if="not field.confirm" type="submit" class="button" name="do"
value=":label" value=":label" title=":descr"
style=":'%s; %s' % (url('action', bg=True), buttonWidth)"/> style=":'%s; %s' % (url('action', bg=True), buttonWidth)"/>
</form>''') </form>''')
@ -78,13 +82,24 @@ class Action(Field):
def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'} def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'}
def callAction(self, obj, method, hasParam, param):
'''Calls p_method on p_obj. m_method can be the single action as defined
in self.action or one of them is self.action contains several
methods. Calling m_method can be done with a p_param (when p_hasParam
is True), ie, when self.confirm is "text".'''
if hasParam: return method(obj, param)
else: return method(obj)
def __call__(self, obj): def __call__(self, obj):
'''Calls the action on p_obj.''' '''Calls the action on p_obj.'''
# Must we call the method(s) with a param ?
hasParam = self.confirm == 'text'
param = hasParam and obj.request.get('comment', None)
if type(self.action) in sutils.sequenceTypes: if type(self.action) in sutils.sequenceTypes:
# There are multiple Python methods # There are multiple Python methods
res = [True, ''] res = [True, '']
for act in self.action: for act in self.action:
actRes = act(obj) actRes = self.callAction(obj, act, hasParam, param)
if type(actRes) in sutils.sequenceTypes: if type(actRes) in sutils.sequenceTypes:
res[0] = res[0] and actRes[0] res[0] = res[0] and actRes[0]
if self.result.startswith('file'): if self.result.startswith('file'):
@ -95,7 +110,7 @@ class Action(Field):
res[0] = res[0] and actRes res[0] = res[0] and actRes
else: else:
# There is only one Python method # There is only one Python method
actRes = self.action(obj) actRes = self.callAction(obj, self.action, hasParam, param)
if type(actRes) in sutils.sequenceTypes: if type(actRes) in sutils.sequenceTypes:
res = list(actRes) res = list(actRes)
else: else:

View file

@ -57,8 +57,8 @@ def sendMail(config, to, subject, body, attachments=None, log=None):
else: else:
msg = '' msg = ''
if log: if log:
log('mail disabled%s: should send mail from %s to %s.' % \ log('mail disabled%s: should send mail from %s to %d ' \
(msg, fromAddress, str(to))) 'recipient(s): %s.' % (msg, fromAddress, len(to), str(to)))
log('subject: %s' % subject) log('subject: %s' % subject)
log('body: %s' % body) log('body: %s' % body)
if attachments and log: log('%d attachment(s).' % len(attachments)) if attachments and log: log('%d attachment(s).' % len(attachments))

View file

@ -816,13 +816,15 @@ class BaseMixin:
cssJs['js'] = js or () cssJs['js'] = js or ()
return res return res
def getAppyTypes(self, layoutType, pageName): def getAppyTypes(self, layoutType, pageName, type=None):
'''Returns the list of fields that belong to a given page (p_pageName) '''Returns the list of fields that belong to a given page (p_pageName)
for a given p_layoutType. If p_pageName is None, fields of all pages for a given p_layoutType. If p_pageName is None, fields of all pages
are returned.''' are returned. If p_type is defined, only fields of this p_type are
returned. '''
res = [] res = []
for field in self.getAllAppyTypes(): for field in self.getAllAppyTypes():
if pageName and (field.page.name != pageName): continue if pageName and (field.page.name != pageName): continue
if type and (field.type != type): continue
if not field.isShowable(self, layoutType): continue if not field.isShowable(self, layoutType): continue
res.append(field) res.append(field)
return res return res

View file

@ -72,6 +72,7 @@ input.buttonSmall { width: 100px !important; font-size: 85%; height: 18px;
.help { cursor: help } .help { cursor: help }
.refLink { font-style: italic; padding-left: 5px; font-size: 90%; color: grey } .refLink { font-style: italic; padding-left: 5px; font-size: 90%; color: grey }
.buttons { margin-left: 4px } .buttons { margin-left: 4px }
.objectButtons { margin-top: 5px }
.message { position: absolute; top: -40px; left: 50%; font-size: 90%; .message { position: absolute; top: -40px; left: 50%; font-size: 90%;
width: 600px; border: 1px #F0C36D solid; padding: 6px; width: 600px; border: 1px #F0C36D solid; padding: 6px;
background-color: #F9EDBE; text-align: center; margin-left: -300px; background-color: #F9EDBE; text-align: center; margin-left: -300px;

View file

@ -381,22 +381,19 @@ class AbstractWrapper(object):
pxTransitions = Px(''' pxTransitions = Px('''
<form var="transitions=targetObj.getTransitions()" if="transitions" <form var="transitions=targetObj.getTransitions()" if="transitions"
var2="formId='trigger_%s' % targetObj.id" method="post" var2="formId='trigger_%s' % targetObj.id" method="post"
id=":formId" action=":targetObj.absolute_url() + '/do'"> id=":formId" action=":targetObj.absolute_url() + '/do'"
style="display: inline">
<input type="hidden" name="action" value="Trigger"/> <input type="hidden" name="action" value="Trigger"/>
<input type="hidden" name="transition"/> <input type="hidden" name="transition"/>
<!-- Input field for storing the comment coming from the popup --> <!-- Input field for storing the comment coming from the popup -->
<textarea id="comment" name="comment" cols="30" rows="3" <textarea id="comment" name="comment" cols="30" rows="3"
style="display:none"></textarea> style="display:none"></textarea>
<table cellpadding="0" cellspacing="0"> <x for="transition in transitions">
<tr valign="middle"> <!-- Render a transition or a group of transitions. -->
<td align=":dright" for="transition in transitions"> <x if="transition.type == 'transition'">:transition.pxView</x>
<!-- Render a transition or a group of transitions. --> <x if="transition.type == 'group'"
<x if="transition.type == 'transition'">:transition.pxView</x> var2="uiGroup=transition">:uiGroup.px</x>
<x if="transition.type == 'group'" </x>
var2="uiGroup=transition">:uiGroup.px</x>
</td>
</tr>
</table>
</form>''') </form>''')
# Displays header information about an object: title, workflow-related info, # Displays header information about an object: title, workflow-related info,
@ -458,103 +455,107 @@ class AbstractWrapper(object):
# Shows the range of buttons (next, previous, save,...) and the workflow # Shows the range of buttons (next, previous, save,...) and the workflow
# transitions for a given object. # transitions for a given object.
pxButtons = Px(''' pxButtons = Px('''
<table cellpadding="2" cellspacing="0" style="margin-top: 7px" <div class="objectButtons" style=":'float: %s' % dleft"
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(); mayAct=not isEdit and zobj.mayAct();
pageInfo=phaseObj.pagesInfo[page]"> pageInfo=phaseObj.pagesInfo[page]">
<tr valign="top"> <!-- Refresh -->
<!-- Refresh --> <a if="zobj.isDebug()"
<td if="zobj.isDebug()"> href=":zobj.getUrl(mode=layoutType, page=page, refresh='yes', \
<a href=":zobj.getUrl(mode=layoutType, page=page, refresh='yes', \ inPopup=inPopup)">
inPopup=inPopup)"> <img title="Refresh" style="vertical-align:top" src=":url('refresh')"/>
<img title="Refresh" style="vertical-align:top" src=":url('refresh')"/> </a>
</a> <!-- Previous -->
</td> <x if="previousPage and pageInfo.showPrevious"
<!-- Previous --> var2="label=_('page_previous');
<td if="previousPage and pageInfo.showPrevious" buttonWidth=ztool.getButtonWidth(label)">
var2="label=_('page_previous'); <!-- Button on the edit page -->
buttonWidth=ztool.getButtonWidth(label)"> <x if="isEdit">
<!-- Button on the edit page --> <input type="button" class="button" value=":label"
<x if="isEdit"> onClick="submitAppyForm('previous')"
<input type="button" class="button" value=":label" style=":'%s; %s' % (url('previous', bg=True), buttonWidth)"/>
onClick="submitAppyForm('previous')" <input type="hidden" name="previousPage" value=":previousPage"/>
style=":'%s; %s' % (url('previous', bg=True), buttonWidth)"/> </x>
<input type="hidden" name="previousPage" value=":previousPage"/> <!-- Button on the view page -->
</x> <input if="not isEdit" type="button" class="button" value=":label"
<!-- Button on the view page --> style=":'%s; %s' % (url('previous', bg=True), buttonWidth)"
<input if="not isEdit" type="button" class="button" value=":label" onclick=":'goto(%s)' % q(zobj.getUrl(page=previousPage, \
style=":'%s; %s' % (url('previous', bg=True), buttonWidth)" inPopup=inPopup))"/>
onclick=":'goto(%s)' % q(zobj.getUrl(page=previousPage, \ </x>
inPopup=inPopup))"/>
</td>
<!-- Save --> <!-- Save -->
<td if="isEdit and pageInfo.showSave"> <input if="isEdit and pageInfo.showSave"
<input type="button" class="button" onClick="submitAppyForm('save')" type="button" class="button" onClick="submitAppyForm('save')"
var="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))" />
</td>
<!-- Cancel -->
<td if="isEdit and pageInfo.showCancel">
<input type="button" class="button" onClick="submitAppyForm('cancel')"
var="label=_('object_cancel')" value=":label"
style=":'%s; %s' % (url('cancel', bg=True), \
ztool.getButtonWidth(label))"/>
</td>
<td if="not isEdit"
var2="locked=zobj.isLocked(user, page);
editable=pageInfo.showOnEdit and pageInfo.showEdit and \
mayAct and zobj.mayEdit()">
<!-- Edit --> <!-- Cancel -->
<input type="button" class="button" if="editable and not locked" <input if="isEdit and pageInfo.showCancel"
var="label=_('object_edit')" value=":label" type="button" class="button" onClick="submitAppyForm('cancel')"
style=":'%s; %s' % (url('edit', bg=True), \ var2="label=_('object_cancel')" value=":label"
ztool.getButtonWidth(label))" style=":'%s; %s' % (url('cancel', bg=True), \
onclick=":'goto(%s)' % q(zobj.getUrl(mode='edit', page=page, \ ztool.getButtonWidth(label))"/>
inPopup=inPopup))"/>
<!-- Locked --> <x if="not isEdit"
<a if="editable and locked"> var2="locked=zobj.isLocked(user, page);
<img style="cursor: help" editable=pageInfo.showOnEdit and pageInfo.showEdit and \
var="lockDate=ztool.formatDate(locked[1]); mayAct and zobj.mayEdit()">
lockMap={'user':ztool.getUserName(locked[0]), \
'date':lockDate};
lockMsg=_('page_locked', mapping=lockMap)"
src=":url('lockedBig')" title=":lockMsg"/></a>
<a if="editable and locked and user.has_role('Manager')">
<img class="clickable" title=":_('page_unlock')"
src=":url('unlockBig')"
onclick=":'onUnlockPage(%s,%s)' % (q(zobj.id), q(page))"/></a>
</td>
<!-- Next --> <!-- Edit -->
<td if="nextPage and pageInfo.showNext" <input type="button" class="button" if="editable and not locked"
var2="label=_('page_next'); var="label=_('object_edit')" value=":label"
buttonWidth=ztool.getButtonWidth(label)"> style=":'%s; %s' % (url('edit', bg=True), \
<!-- Button on the edit page --> ztool.getButtonWidth(label))"
<x if="isEdit"> onclick=":'goto(%s)' % q(zobj.getUrl(mode='edit', page=page, \
<input type="button" class="button" onClick="submitAppyForm('next')" inPopup=inPopup))"/>
style=":'%s; %s' % (url('next', bg=True), buttonWidth)"
value=":label"/> <!-- Locked -->
<input type="hidden" name="nextPage" value=":nextPage"/> <a if="editable and locked">
</x> <img style="cursor: help"
<!-- Button on the view page --> var="lockDate=ztool.formatDate(locked[1]);
<input if="not isEdit" type="button" class="button" value=":label" lockMap={'user':ztool.getUserName(locked[0]), \
'date':lockDate};
lockMsg=_('page_locked', mapping=lockMap)"
src=":url('lockedBig')" title=":lockMsg"/></a>
<a if="editable and locked and user.has_role('Manager')">
<img class="clickable" title=":_('page_unlock')"
src=":url('unlockBig')"
onclick=":'onUnlockPage(%s,%s)' % (q(zobj.id), q(page))"/></a>
</x>
<!-- Next -->
<x if="nextPage and pageInfo.showNext"
var2="label=_('page_next');
buttonWidth=ztool.getButtonWidth(label)">
<!-- Button on the edit page -->
<x if="isEdit">
<input type="button" class="button" onClick="submitAppyForm('next')"
style=":'%s; %s' % (url('next', bg=True), buttonWidth)" style=":'%s; %s' % (url('next', bg=True), buttonWidth)"
onclick=":'goto(%s)' % q(zobj.getUrl(page=nextPage, \ value=":label"/>
inPopup=inPopup))"/> <input type="hidden" name="nextPage" value=":nextPage"/>
</td> </x>
<!-- Button on the view page -->
<input if="not isEdit" type="button" class="button" value=":label"
style=":'%s; %s' % (url('next', bg=True), buttonWidth)"
onclick=":'goto(%s)' % q(zobj.getUrl(page=nextPage, \
inPopup=inPopup))"/>
</x>
<!-- Workflow transitions --> <!-- Workflow transitions -->
<td var="targetObj=zobj; buttonsMode='normal'" <x var="targetObj=zobj; buttonsMode='normal'"
if="mayAct and \ if="mayAct and \
targetObj.showTransitions(layoutType)">:obj.pxTransitions</td> targetObj.showTransitions(layoutType)">:obj.pxTransitions</x>
</tr> </div>
</table>''') <!-- Fields (actions) defined with layout "buttons" -->
<x if="layoutType != 'edit'">
<div class="objectButtons" style=":'float: %s' % dleft"
var="fields=zobj.getAppyTypes('buttons', page, type='Action');
layoutType='view'" if="fields">
<x for="field in fields">:field.pxRender</x></div>
</x>''')
# Displays the fields of a given page for a given object. # Displays the fields of a given page for a given object.
pxFields = Px(''' pxFields = Px('''
@ -1199,4 +1200,28 @@ class AbstractWrapper(object):
return localRoles return localRoles
def raiseUnauthorized(self, msg=None): return self.o.raiseUnauthorized(msg) def raiseUnauthorized(self, msg=None): return self.o.raiseUnauthorized(msg)
def sendMailIf(self, privilege, subject, body, attachments=None,
isRole=False):
'''Sends a mail related to this object to any active user having
p_privilege on it. If p_isRole is False, p_privilege is a permission.
Else, it is a role.'''
# Determine the list of recipients based on active users having
# p_privilege on p_self.
recipients = []
for user in self.tool.users:
if (user.state == 'inactive'): continue
# Check if the user has p_privilege on this object
hasPrivilege = isRole and user.has_role or user.has_permission
if not hasPrivilege(privilege, self): continue
# Get the mail recipient for this user
recipient = user.getMailRecipient()
if not recipient: continue
recipients.append(recipient)
if recipients:
self.tool.sendMail(recipients, subject, body, attachments)
else:
name = isRole and 'role' or 'permission'
self.log('no recipient for sending mail about %s with %s %s.' % \
(self.id, name, privilege))
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------