[gen] Bugfix in the Ref field; added method workflow.Transition.getBack that finds the 'back' transition of a given transition.
This commit is contained in:
parent
14f85509e1
commit
1d0ee7a614
|
@ -219,8 +219,7 @@ class UiGroup:
|
|||
</tr>
|
||||
<!-- The rows of widgets -->
|
||||
<tr valign=":field.valign" for="row in field.elements">
|
||||
<td for="field in row"
|
||||
colspan="field.colspan"
|
||||
<td for="field in row" colspan=":field.colspan"
|
||||
style=":not loop.field.last and ('padding-right:%s'% cellgap) or ''">
|
||||
<x if="field">
|
||||
<x if="field.type == 'group'">:field.pxView</x>
|
||||
|
|
|
@ -195,10 +195,6 @@ class Ref(Field):
|
|||
|
||||
# PX that displays referred objects as a list.
|
||||
pxViewList = Px('''
|
||||
<!-- No object at all -->
|
||||
<div if="not objects" class="smaller">:_('no_ref')</div>
|
||||
|
||||
<x if="objects">
|
||||
<div if="not innerRef or showPlusIcon" style="margin-bottom: 4px">
|
||||
<span if="subLabel" class="discreet">:_(subLabel)</span>
|
||||
(<span class="discreet">:totalNumber</span>)
|
||||
|
@ -217,6 +213,9 @@ class Ref(Field):
|
|||
<!-- (Top) navigation -->
|
||||
<x>:tool.pxNavigate</x>
|
||||
|
||||
<!-- No object is present -->
|
||||
<p class="discreet" if="not objects and not showPlusIcon">:_('no_ref')</p>
|
||||
|
||||
<!-- Linked objects -->
|
||||
<table if="objects" class=":not innerRef and 'list' or ''"
|
||||
width=":innerRef and '100%' or field.layouts['view'].width"
|
||||
|
@ -273,8 +272,8 @@ class Ref(Field):
|
|||
|
||||
<!-- Init checkboxes if present. -->
|
||||
<script if="checkboxes"
|
||||
type="text/javascript">:'initRefCbs(%s)' % q(ajaxHookId)</script>
|
||||
</x>''')
|
||||
type="text/javascript">:'initRefCbs(%s)' % q(ajaxHookId)
|
||||
</script>''')
|
||||
|
||||
# PX that displays the list of objects the user may select to insert into a
|
||||
# ref field with link="list".
|
||||
|
@ -410,7 +409,7 @@ class Ref(Field):
|
|||
title=field.getReferenceLabel(tied, unlimited=True)"
|
||||
selected=":inRequest and (uid in requestValue) or \
|
||||
(uid in uids)" value=":uid"
|
||||
title=":title">:ztool.truncateValue(title, field.swidth)</option>
|
||||
title=":title">:ztool.truncateValue(title, field.width)</option>
|
||||
</select>''')
|
||||
|
||||
pxSearch = Px('''
|
||||
|
|
|
@ -397,6 +397,30 @@ class Transition:
|
|||
if not obj.isTemporary(): obj.reindex()
|
||||
return tool.goto(obj.getUrl(rq['HTTP_REFERER']))
|
||||
|
||||
@staticmethod
|
||||
def getBack(workflow, transition):
|
||||
'''Returns the name of the transition (in p_workflow) that "cancels" the
|
||||
triggering of p_transition and allows to go back to p_transition's
|
||||
start state.'''
|
||||
# Get the end state(s) of p_transition
|
||||
transition = getattr(workflow, transition)
|
||||
# Browse all transitions and find the one starting at p_transition's end
|
||||
# state and coming back to p_transition's start state.
|
||||
for trName, tr in workflow.__dict__.iteritems():
|
||||
if not isinstance(tr, Transition) or (tr == transition): continue
|
||||
if transition.isSingle():
|
||||
if tr.hasState(transition.states[1], True) and \
|
||||
tr.hasState(transition.states[0], False): return trName
|
||||
else:
|
||||
startOk = False
|
||||
endOk = False
|
||||
for start, end in transition.states:
|
||||
if (not startOk) and tr.hasState(end, True):
|
||||
startOk = True
|
||||
if (not endOk) and tr.hasState(start, False):
|
||||
endOk = True
|
||||
if startOk and endOk: return trName
|
||||
|
||||
class UiTransition:
|
||||
'''Represents a widget that displays a transition.'''
|
||||
pxView = Px('''<x var="buttonCss = (buttonsMode == 'small') and \
|
||||
|
|
|
@ -18,8 +18,9 @@ try:
|
|||
except ImportError:
|
||||
_noroles = []
|
||||
|
||||
# Errors -----------------------------------------------------------------------
|
||||
jsMessages = ('no_elem_selected', 'action_confirm', 'warn_leave_form')
|
||||
# Global JS internationalized messages that will be computed in every page -----
|
||||
jsMessages = ('no_elem_selected', 'action_confirm', 'save_confirm',
|
||||
'warn_leave_form')
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ToolMixin(BaseMixin):
|
||||
|
@ -389,25 +390,26 @@ class ToolMixin(BaseMixin):
|
|||
k = self.getAppyClass(className)
|
||||
return hasattr(k, 'listColumns') and k.listColumns or ('title',)
|
||||
|
||||
def truncateValue(self, value, width=15):
|
||||
'''Truncates the p_value according to p_width.'''
|
||||
def truncateValue(self, value, width=20):
|
||||
'''Truncates the p_value according to p_width. p_value has to be
|
||||
unicode-encoded for being truncated (else, one char may be spread on
|
||||
2 chars).'''
|
||||
# Param p_width can be None.
|
||||
if not width: width = 20
|
||||
if isinstance(value, str): value = value.decode('utf-8')
|
||||
if len(value) > width:
|
||||
return value[:width].encode('utf-8') + '...'
|
||||
return value.encode('utf-8')
|
||||
if len(value) > width: return value[:width] + '...'
|
||||
return value
|
||||
|
||||
def truncateText(self, text, width=15):
|
||||
def truncateText(self, text, width=20):
|
||||
'''Truncates p_text to max p_width chars. If the text is longer than
|
||||
p_width, the truncated part is put in a "acronym" html tag.'''
|
||||
# p_text has to be unicode-encoded for being truncated (else, one char
|
||||
# may be spread on 2 chars). But this method must return an encoded
|
||||
# string, else, ZPT crashes. The same remark holds for m_truncateValue
|
||||
# above.
|
||||
uText = text # uText will store the unicode version
|
||||
if isinstance(text, str): uText = text.decode('utf-8')
|
||||
if len(uText) <= width: return text
|
||||
return '<acronym title="%s">%s</acronym>' % \
|
||||
(text, uText[:width].encode('utf-8') + '...')
|
||||
p_width, the truncated part is put in a "acronym" html tag. p_text
|
||||
has to be unicode-encoded for being truncated (else, one char may be
|
||||
spread on 2 chars).'''
|
||||
# Param p_width can be None.
|
||||
if not width: width = 20
|
||||
if isinstance(text, str): text = text.decode('utf-8')
|
||||
if len(text) <= width: return text
|
||||
return '<acronym title="%s">%s...</acronym>' % (text, text[:width])
|
||||
|
||||
def splitList(self, l, sub):
|
||||
'''Returns a list made of the same elements as p_l, but grouped into
|
||||
|
@ -1226,13 +1228,13 @@ class ToolMixin(BaseMixin):
|
|||
return [f for f in self.getAllAppyTypes(contentType) \
|
||||
if (f.type == 'Pod') and (f.show == 'result')]
|
||||
|
||||
def formatDate(self, aDate, withHour=True):
|
||||
'''Returns aDate formatted as specified by tool.dateFormat.
|
||||
def formatDate(self, date, withHour=True):
|
||||
'''Returns p_date formatted as specified by tool.dateFormat.
|
||||
If p_withHour is True, hour is appended, with a format specified
|
||||
in tool.hourFormat.'''
|
||||
tool = self.appy()
|
||||
res = aDate.strftime(tool.dateFormat)
|
||||
if withHour: res += ' (%s)' % aDate.strftime(tool.hourFormat)
|
||||
res = date.strftime(tool.dateFormat)
|
||||
if withHour: res += ' (%s)' % date.strftime(tool.hourFormat)
|
||||
return res
|
||||
|
||||
def generateUid(self, className):
|
||||
|
|
|
@ -942,12 +942,11 @@ class BaseMixin:
|
|||
if not name: return wf
|
||||
return WorkflowDescriptor.getWorkflowName(wf)
|
||||
|
||||
def getWorkflowLabel(self, stateName=None):
|
||||
'''Gets the i18n label for p_stateName, or for the current object state
|
||||
if p_stateName is not given. Note that if p_stateName is given, it
|
||||
can also represent the name of a transition.'''
|
||||
stateName = stateName or self.State()
|
||||
return '%s_%s' % (self.getWorkflow(name=True), stateName)
|
||||
def getWorkflowLabel(self, name=None):
|
||||
'''Gets the i18n label for p_name (which can denote a state or a
|
||||
transition), or for the current object state if p_name is None.'''
|
||||
name = name or self.State()
|
||||
return '%s_%s' % (self.getWorkflow(name=True), name)
|
||||
|
||||
def getTransitions(self, includeFake=True, includeNotShowable=False,
|
||||
grouped=True):
|
||||
|
|
|
@ -203,7 +203,7 @@ class Translation(ModelClass):
|
|||
title = gen.String(show=False, indexed=True,
|
||||
page=gen.Page('main',label='Main'))
|
||||
def getPoFile(self): pass
|
||||
po = gen.Action(action=getPoFile, result='filetmp')
|
||||
po = gen.Action(action=getPoFile, result='file')
|
||||
sourceLanguage = gen.String(width=4)
|
||||
def label(self): pass
|
||||
def show(self, name): pass
|
||||
|
|
|
@ -247,6 +247,10 @@ msgstr ""
|
|||
msgid "action_null"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr ""
|
||||
|
|
|
@ -247,6 +247,10 @@ msgstr ""
|
|||
msgid "action_null"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr ""
|
||||
|
|
|
@ -247,6 +247,10 @@ msgstr ""
|
|||
msgid "action_null"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr "Zurück zum Anfang"
|
||||
|
|
|
@ -248,6 +248,10 @@ msgstr "Action could not be performed on ${nb} element(s)."
|
|||
msgid "action_null"
|
||||
msgstr "Action had no effect."
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr "Are you sure you want to apply this change?"
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr "Go to top"
|
||||
|
|
|
@ -247,6 +247,10 @@ msgstr ""
|
|||
msgid "action_null"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr "Ir al inicio"
|
||||
|
|
|
@ -248,6 +248,10 @@ msgstr "L'action n'a pu être réalisée pour ${nb} élément(s)."
|
|||
msgid "action_null"
|
||||
msgstr "L'action n'a eu aucun effet."
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr "Êtes-vous sûr de vouloir appliquer ce changement?"
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr "Aller au début"
|
||||
|
|
|
@ -247,6 +247,10 @@ msgstr ""
|
|||
msgid "action_null"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr "Andare all'inizio"
|
||||
|
|
|
@ -247,6 +247,10 @@ msgstr ""
|
|||
msgid "action_null"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Are you sure you want to apply this change?"
|
||||
msgid "save_confirm"
|
||||
msgstr ""
|
||||
|
||||
#. Default: "Go to top"
|
||||
msgid "goto_first"
|
||||
msgstr "Ga naar het begin"
|
||||
|
|
|
@ -62,7 +62,7 @@ img { border: 0; vertical-align: middle }
|
|||
.userStripText { padding: 0 0.3em 0 0.3em; color: white }
|
||||
.userStrip a { color: #e7e7e7 }
|
||||
.userStrip a:visited { color: #e7e7e7 }
|
||||
.breadcrumb { font-size: 11pt }
|
||||
.breadcrumb { font-size: 11pt; padding-bottom: 6px }
|
||||
.login { margin: 3px; color: black }
|
||||
input.button { color: #666666; height: 20px; width: 130px;
|
||||
cursor:pointer; font-size: 90%; padding: 1px 0 0 10px;
|
||||
|
@ -104,12 +104,12 @@ td.search { padding-top: 8px }
|
|||
.dropdownMenu { cursor: pointer; padding-right: 4px; font-size: 93% }
|
||||
.dropdown a:hover { text-decoration: underline }
|
||||
.list { margin-bottom: 3px }
|
||||
.list td, .list th { border: 1px solid grey;
|
||||
padding-left: 5px; padding-right: 5px; padding-top: 3px }
|
||||
.list td, .list th { border: 3px solid #ededed; color: grey;
|
||||
padding: 3px 5px 0 5px }
|
||||
.list th { background-color: #e5e5e5; font-style: italic; font-weight: normal }
|
||||
.grid th { font-style: italic; font-weight: normal;
|
||||
border-bottom: 2px solid grey; padding: 2px 2px }
|
||||
.grid td { padding-right: 5px }
|
||||
border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px }
|
||||
.grid td { padding: 3px 3px 0 3px }
|
||||
.cellGap { padding-right: 0.4em }
|
||||
.cellDashed { border: 1px dashed grey !important }
|
||||
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important }
|
||||
|
@ -127,7 +127,8 @@ td.search { padding-top: 8px }
|
|||
color: white }
|
||||
.even { background-color: #f9f9f9 }
|
||||
.odd { background-color: #f4f4f4 }
|
||||
.summary { margin-bottom: 5px; background-color: #f9f9f9 }
|
||||
.summary { margin-bottom: 5px; background-color: #f9f9f9;
|
||||
border: 2px solid #f9f9f9 }
|
||||
.by { padding: 5px; color: grey; font-size: 97% }
|
||||
.underline { border-bottom: 1px dotted grey }
|
||||
.state { font-weight: bold; border-bottom: 1px dashed grey }
|
||||
|
|
|
@ -279,7 +279,7 @@ function doInlineSave(objectUid, name, objectUrl, content){
|
|||
/* Ajax-saves p_content of field named p_name on object whose id is
|
||||
p_objectUid and whose URL is p_objectUrl. Asks a confirmation before
|
||||
doing it. */
|
||||
var doIt = confirm('Do it?');
|
||||
var doIt = confirm(save_confirm);
|
||||
var params = {'action': 'storeFromAjax', 'layoutType': 'view'};
|
||||
var hook = null;
|
||||
if (!doIt) {
|
||||
|
|
|
@ -587,6 +587,14 @@ class ToolWrapper(AbstractWrapper):
|
|||
'''Sends a mail. See doc for appy.gen.mail.sendMail.'''
|
||||
sendMail(self, to, subject, body, attachments=attachments)
|
||||
|
||||
def formatDate(self, date, withHour=True):
|
||||
'''Check doc @ToolMixin::formatDate.'''
|
||||
if not date: return
|
||||
return self.o.formatDate(date, withHour=withHour)
|
||||
|
||||
def getUserName(self, login=None, normalized=False):
|
||||
return self.o.getUserName(login=login, normalized=normalized)
|
||||
|
||||
def refreshCatalog(self, startObject=None):
|
||||
'''Reindex all Appy objects. For some unknown reason, method
|
||||
catalog.refreshCatalog is not able to recatalog Appy objects.'''
|
||||
|
|
|
@ -329,18 +329,17 @@ class AbstractWrapper(object):
|
|||
<th align=":dleft">:_('action_comment')</th>
|
||||
</tr>
|
||||
<tr for="event in objs"
|
||||
var2="odd=loop.event.odd;
|
||||
rhComments=event.get('comments', None);
|
||||
var2="rhComments=event.get('comments', None);
|
||||
state=event.get('review_state', None);
|
||||
action=event['action'];
|
||||
isDataChange=action == '_datachange_'"
|
||||
class="odd and 'even' or 'odd'" valign="top">
|
||||
class=":loop.event.odd and 'even' or 'odd'" valign="top">
|
||||
<td if="isDataChange">
|
||||
<x>:_('data_change')</x>
|
||||
<img if="user.has_role('Manager')" class="clickable"
|
||||
src=":url('delete')"
|
||||
onclick=":'onDeleteEvent(%s,%s)' % \
|
||||
(q(zobj.UID()), q(event['time']))"/>
|
||||
(q(zobj.id), q(event['time']))"/>
|
||||
</td>
|
||||
<td if="not isDataChange">:_(zobj.getWorkflowLabel(action))</td>
|
||||
<td var="actorId=event.get('actor')">
|
||||
|
@ -408,7 +407,7 @@ class AbstractWrapper(object):
|
|||
<img class="clickable" onclick="toggleCookie('appyHistory')"
|
||||
src=":historyExpanded and url('collapse.gif') or url('expand.gif')"
|
||||
align=":dleft" id="appyHistory_img" style="padding-right:4px"/>
|
||||
<x>:_('object_history')</x> ||
|
||||
<x>:_('object_history')</x> —
|
||||
</x>
|
||||
|
||||
<!-- Creator and last modification date -->
|
||||
|
@ -437,7 +436,7 @@ class AbstractWrapper(object):
|
|||
<td colspan="2">
|
||||
<span id="appyHistory"
|
||||
style=":historyExpanded and 'display:block' or 'display:none'">
|
||||
<div var="ajaxHookId=zobj.UID() + '_history'" id=":ajaxHookId">
|
||||
<div var="ajaxHookId=zobj.id + '_history'" id=":ajaxHookId">
|
||||
<script type="text/javascript">::'askObjectHistory(%s,%s,%d,0)' % \
|
||||
(q(ajaxHookId), q(zobj.absolute_url()), \
|
||||
historyMaxPerPage)</script>
|
||||
|
@ -701,9 +700,10 @@ class AbstractWrapper(object):
|
|||
elif name == 'session': return self.o.REQUEST.SESSION
|
||||
elif name == 'typeName': return self.__class__.__bases__[-1].__name__
|
||||
elif name == 'id': return self.o.id
|
||||
elif name == 'uid': return self.o.UID()
|
||||
elif name == 'uid': return self.o.id
|
||||
elif name == 'klass': return self.__class__.__bases__[-1]
|
||||
elif name == 'created': return self.o.created
|
||||
elif name == 'creator': return self.o.creator
|
||||
elif name == 'modified': return self.o.modified
|
||||
elif name == 'url': return self.o.absolute_url()
|
||||
elif name == 'state': return self.o.State()
|
||||
|
@ -751,6 +751,14 @@ class AbstractWrapper(object):
|
|||
if custom: return custom(self, *args, **kwargs)
|
||||
|
||||
def getField(self, name): return self.o.getAppyType(name)
|
||||
def getLabel(self, name, type='field'):
|
||||
'''Gets the translated label of field named p_name. If p_type is
|
||||
"workflow", p_name denotes a workflow state or transition, not a
|
||||
field.'''
|
||||
o = self.o
|
||||
if type == 'field': return o.translate(o.getAppyType(name).labelId)
|
||||
elif type == 'workflow': return o.translate(o.getWorkflowLabel(name))
|
||||
|
||||
def isEmpty(self, name):
|
||||
'''Returns True if value of field p_name is considered as being
|
||||
empty.'''
|
||||
|
@ -1004,7 +1012,7 @@ class AbstractWrapper(object):
|
|||
# Determine where to put the result
|
||||
toDisk = (at != 'string')
|
||||
if toDisk and not at:
|
||||
at = getOsTempFolder() + '/' + self.o.UID() + '.xml'
|
||||
at = getOsTempFolder() + '/' + self.o.id + '.xml'
|
||||
# Create the XML version of the object
|
||||
marshaller = XmlMarshaller(cdata=True, dumpUnicode=True,
|
||||
dumpXmlPrologue=toDisk,
|
||||
|
@ -1036,7 +1044,7 @@ class AbstractWrapper(object):
|
|||
whose values are the previous field values.'''
|
||||
self.o.addDataChange(data)
|
||||
|
||||
def getLastEvent(self, transition, notBefore=''):
|
||||
def getLastEvent(self, transition, notBefore=None):
|
||||
'''Gets, from the object history, the last occurrence of transition
|
||||
named p_transition. p_transition can be a list of names: in this
|
||||
case, it returns the most recent occurrence of those transitions. If
|
||||
|
@ -1069,4 +1077,10 @@ class AbstractWrapper(object):
|
|||
if getattr(workflow, name).__class__.__name__ != 'State': continue
|
||||
res.append((name, o.translate(o.getWorkflowLabel(name))))
|
||||
return res
|
||||
|
||||
def path(self, name):
|
||||
'''Returns the absolute file name of file stored in File field p_nnamed
|
||||
p_name.'''
|
||||
v = getattr(self, name)
|
||||
if v: return v.getFilePath(self)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -288,17 +288,18 @@ class Renderer:
|
|||
supposed to be in binary format in p_content. The document
|
||||
p_format may be: odt or any format in imageFormats.
|
||||
|
||||
p_anchor, p_wrapInPara and p_size are only relevant for images:
|
||||
p_anchor, p_wrapInPara and p_size, p_sizeUnit and p_style are only
|
||||
relevant for images:
|
||||
* p_anchor defines the way the image is anchored into the document;
|
||||
Valid values are 'page','paragraph', 'char' and 'as-char';
|
||||
* p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p'
|
||||
tag;
|
||||
* p_size, if specified, is a tuple of float or integers
|
||||
(width, height) expressing size in p_sizeUnit (see below).
|
||||
If not specified, size will be computed from image info.
|
||||
If not specified, size will be computed from image info;
|
||||
* p_sizeUnit is the unit for p_size elements, it can be "cm"
|
||||
(centimeters) or "px" (pixels).
|
||||
* If p_style is given, it is the content of a "style" attribute,
|
||||
(centimeters) or "px" (pixels);
|
||||
* if p_style is given, it is the content of a "style" attribute,
|
||||
containing CSS attributes. If "width" and "heigth" attributes are
|
||||
found there, they will override p_size and p_sizeUnit.
|
||||
|
||||
|
@ -308,8 +309,7 @@ class Renderer:
|
|||
'''
|
||||
importer = None
|
||||
# Is there someting to import?
|
||||
if not content and not at:
|
||||
raise PodError(DOC_NOT_SPECIFIED)
|
||||
if not content and not at: raise PodError(DOC_NOT_SPECIFIED)
|
||||
# Convert Zope files into Appy wrappers.
|
||||
if content.__class__.__name__ in ('File', 'Image'):
|
||||
content = FileWrapper(content)
|
||||
|
|
Loading…
Reference in a new issue