[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:
Gaetan Delannay 2014-05-02 12:35:09 +02:00
parent 14f85509e1
commit 1d0ee7a614
19 changed files with 206 additions and 128 deletions

View file

@ -219,8 +219,7 @@ class UiGroup:
</tr> </tr>
<!-- The rows of widgets --> <!-- The rows of widgets -->
<tr valign=":field.valign" for="row in field.elements"> <tr valign=":field.valign" for="row in field.elements">
<td for="field in row" <td for="field in row" colspan=":field.colspan"
colspan="field.colspan"
style=":not loop.field.last and ('padding-right:%s'% cellgap) or ''"> style=":not loop.field.last and ('padding-right:%s'% cellgap) or ''">
<x if="field"> <x if="field">
<x if="field.type == 'group'">:field.pxView</x> <x if="field.type == 'group'">:field.pxView</x>

View file

@ -195,86 +195,85 @@ class Ref(Field):
# PX that displays referred objects as a list. # PX that displays referred objects as a list.
pxViewList = Px(''' pxViewList = Px('''
<!-- No object at all --> <div if="not innerRef or showPlusIcon" style="margin-bottom: 4px">
<div if="not objects" class="smaller">:_('no_ref')</div> <span if="subLabel" class="discreet">:_(subLabel)</span>
(<span class="discreet">:totalNumber</span>)
<x>:field.pxAdd</x>
<!-- The search button if field is queryable -->
<input if="objects and field.queryable" type="button"
class="buttonSmall button"
var2="label=_('search_button')" value=":label"
style=":'%s; %s' % (url('search', bg=True), \
ztool.getButtonWidth(label))"
onclick=":'goto(%s)' % \
q('%s/search?className=%s&amp;ref=%s:%s' % \
(ztool.absolute_url(), tiedClassName, zobj.id, field.name))"/>
</div>
<x if="objects"> <!-- (Top) navigation -->
<div if="not innerRef or showPlusIcon" style="margin-bottom: 4px"> <x>:tool.pxNavigate</x>
<span if="subLabel" class="discreet">:_(subLabel)</span>
(<span class="discreet">:totalNumber</span>)
<x>:field.pxAdd</x>
<!-- The search button if field is queryable -->
<input if="objects and field.queryable" type="button"
class="buttonSmall button"
var2="label=_('search_button')" value=":label"
style=":'%s; %s' % (url('search', bg=True), \
ztool.getButtonWidth(label))"
onclick=":'goto(%s)' % \
q('%s/search?className=%s&amp;ref=%s:%s' % \
(ztool.absolute_url(), tiedClassName, zobj.id, field.name))"/>
</div>
<!-- (Top) navigation --> <!-- No object is present -->
<x>:tool.pxNavigate</x> <p class="discreet" if="not objects and not showPlusIcon">:_('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 ''"
width=":innerRef and '100%' or field.layouts['view'].width" width=":innerRef and '100%' or field.layouts['view'].width"
var2="columns=ztool.getColumnsSpecifiers(tiedClassName, \ var2="columns=ztool.getColumnsSpecifiers(tiedClassName, \
field.shownInfo, dir)"> field.shownInfo, dir)">
<tr if="field.showHeaders"> <tr if="field.showHeaders">
<th if="not inPickList and numbered" width=":numbered"></th> <th if="not inPickList and numbered" width=":numbered"></th>
<th for="column in columns" width=":column.width" <th for="column in columns" width=":column.width"
align=":column.align" var2="refField=column.field"> align=":column.align" var2="refField=column.field">
<span>:_(refField.labelId)</span> <span>:_(refField.labelId)</span>
<x>:field.pxSortIcons</x> <x>:field.pxSortIcons</x>
<x var="className=tiedClassName; <x var="className=tiedClassName;
field=refField">:tool.pxShowDetails</x> field=refField">:tool.pxShowDetails</x>
</th> </th>
<th if="checkboxes" class="cbCell"> <th if="checkboxes" class="cbCell">
<img src=":url('checkall')" class="clickable" <img src=":url('checkall')" class="clickable"
title=":_('check_uncheck')" title=":_('check_uncheck')"
onclick=":'toggleAllRefCbs(%s)' % q(ajaxHookId)"/> onclick=":'toggleAllRefCbs(%s)' % q(ajaxHookId)"/>
</th> </th>
</tr> </tr>
<!-- Loop on every (tied or selectable) object. --> <!-- Loop on every (tied or selectable) object. -->
<tr for="tied in objects" valign="top" <tr for="tied in objects" valign="top"
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">
<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">
<!-- The "title" field --> <!-- The "title" field -->
<x if="refField.name == 'title'"> <x if="refField.name == 'title'">
<x>:field.pxObjectTitle</x> <x>:field.pxObjectTitle</x>
<div if="tied.o.mayAct()">:field.pxObjectActions</div> <div if="tied.o.mayAct()">:field.pxObjectActions</div>
</x> </x>
<!-- Any other field --> <!-- Any other field -->
<x if="refField.name != 'title'"> <x if="refField.name != 'title'">
<x var="zobj=tied.o; obj=tied; layoutType='cell'; <x var="zobj=tied.o; obj=tied; layoutType='cell';
innerRef=True; field=refField" innerRef=True; field=refField"
if="field.isShowable(zobj, 'result')">:field.pxRender</x> if="field.isShowable(zobj, 'result')">:field.pxRender</x>
</x> </x>
</td> </td>
<td if="checkboxes" class="cbCell"> <td if="checkboxes" class="cbCell">
<input type="checkbox" name=":ajaxHookId" checked="checked" <input 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 &gt; 1)" <div if="canWrite and (totalNumber &gt; 1)"
align=":dright">:field.pxGlobalActions</div> align=":dright">:field.pxGlobalActions</div>
<!-- (Bottom) navigation --> <!-- (Bottom) navigation -->
<x>:tool.pxNavigate</x> <x>:tool.pxNavigate</x>
<!-- Init checkboxes if present. --> <!-- Init checkboxes if present. -->
<script if="checkboxes" <script if="checkboxes"
type="text/javascript">:'initRefCbs(%s)' % q(ajaxHookId)</script> type="text/javascript">:'initRefCbs(%s)' % q(ajaxHookId)
</x>''') </script>''')
# PX that displays the list of objects the user may select to insert into a # PX that displays the list of objects the user may select to insert into a
# ref field with link="list". # ref field with link="list".
@ -410,7 +409,7 @@ class Ref(Field):
title=field.getReferenceLabel(tied, unlimited=True)" title=field.getReferenceLabel(tied, unlimited=True)"
selected=":inRequest and (uid in requestValue) or \ selected=":inRequest and (uid in requestValue) or \
(uid in uids)" value=":uid" (uid in uids)" value=":uid"
title=":title">:ztool.truncateValue(title, field.swidth)</option> title=":title">:ztool.truncateValue(title, field.width)</option>
</select>''') </select>''')
pxSearch = Px(''' pxSearch = Px('''

View file

@ -397,6 +397,30 @@ class Transition:
if not obj.isTemporary(): obj.reindex() if not obj.isTemporary(): obj.reindex()
return tool.goto(obj.getUrl(rq['HTTP_REFERER'])) 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: class UiTransition:
'''Represents a widget that displays a transition.''' '''Represents a widget that displays a transition.'''
pxView = Px('''<x var="buttonCss = (buttonsMode == 'small') and \ pxView = Px('''<x var="buttonCss = (buttonsMode == 'small') and \

View file

@ -18,8 +18,9 @@ try:
except ImportError: except ImportError:
_noroles = [] _noroles = []
# Errors ----------------------------------------------------------------------- # Global JS internationalized messages that will be computed in every page -----
jsMessages = ('no_elem_selected', 'action_confirm', 'warn_leave_form') jsMessages = ('no_elem_selected', 'action_confirm', 'save_confirm',
'warn_leave_form')
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class ToolMixin(BaseMixin): class ToolMixin(BaseMixin):
@ -389,25 +390,26 @@ class ToolMixin(BaseMixin):
k = self.getAppyClass(className) k = self.getAppyClass(className)
return hasattr(k, 'listColumns') and k.listColumns or ('title',) return hasattr(k, 'listColumns') and k.listColumns or ('title',)
def truncateValue(self, value, width=15): def truncateValue(self, value, width=20):
'''Truncates the p_value according to p_width.''' '''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 isinstance(value, str): value = value.decode('utf-8')
if len(value) > width: if len(value) > width: return value[:width] + '...'
return value[:width].encode('utf-8') + '...' return value
return value.encode('utf-8')
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 '''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_width, the truncated part is put in a "acronym" html tag. p_text
# p_text has to be unicode-encoded for being truncated (else, one char has to be unicode-encoded for being truncated (else, one char may be
# may be spread on 2 chars). But this method must return an encoded spread on 2 chars).'''
# string, else, ZPT crashes. The same remark holds for m_truncateValue # Param p_width can be None.
# above. if not width: width = 20
uText = text # uText will store the unicode version if isinstance(text, str): text = text.decode('utf-8')
if isinstance(text, str): uText = text.decode('utf-8') if len(text) <= width: return text
if len(uText) <= width: return text return '<acronym title="%s">%s...</acronym>' % (text, text[:width])
return '<acronym title="%s">%s</acronym>' % \
(text, uText[:width].encode('utf-8') + '...')
def splitList(self, l, sub): def splitList(self, l, sub):
'''Returns a list made of the same elements as p_l, but grouped into '''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) \ return [f for f in self.getAllAppyTypes(contentType) \
if (f.type == 'Pod') and (f.show == 'result')] if (f.type == 'Pod') and (f.show == 'result')]
def formatDate(self, aDate, withHour=True): def formatDate(self, date, withHour=True):
'''Returns aDate formatted as specified by tool.dateFormat. '''Returns p_date formatted as specified by tool.dateFormat.
If p_withHour is True, hour is appended, with a format specified If p_withHour is True, hour is appended, with a format specified
in tool.hourFormat.''' in tool.hourFormat.'''
tool = self.appy() tool = self.appy()
res = aDate.strftime(tool.dateFormat) res = date.strftime(tool.dateFormat)
if withHour: res += ' (%s)' % aDate.strftime(tool.hourFormat) if withHour: res += ' (%s)' % date.strftime(tool.hourFormat)
return res return res
def generateUid(self, className): def generateUid(self, className):

View file

@ -942,12 +942,11 @@ class BaseMixin:
if not name: return wf if not name: return wf
return WorkflowDescriptor.getWorkflowName(wf) return WorkflowDescriptor.getWorkflowName(wf)
def getWorkflowLabel(self, stateName=None): def getWorkflowLabel(self, name=None):
'''Gets the i18n label for p_stateName, or for the current object state '''Gets the i18n label for p_name (which can denote a state or a
if p_stateName is not given. Note that if p_stateName is given, it transition), or for the current object state if p_name is None.'''
can also represent the name of a transition.''' name = name or self.State()
stateName = stateName or self.State() return '%s_%s' % (self.getWorkflow(name=True), name)
return '%s_%s' % (self.getWorkflow(name=True), stateName)
def getTransitions(self, includeFake=True, includeNotShowable=False, def getTransitions(self, includeFake=True, includeNotShowable=False,
grouped=True): grouped=True):

View file

@ -203,7 +203,7 @@ class Translation(ModelClass):
title = gen.String(show=False, indexed=True, title = gen.String(show=False, indexed=True,
page=gen.Page('main',label='Main')) page=gen.Page('main',label='Main'))
def getPoFile(self): pass def getPoFile(self): pass
po = gen.Action(action=getPoFile, result='filetmp') po = gen.Action(action=getPoFile, result='file')
sourceLanguage = gen.String(width=4) sourceLanguage = gen.String(width=4)
def label(self): pass def label(self): pass
def show(self, name): pass def show(self, name): pass

View file

@ -247,6 +247,10 @@ msgstr ""
msgid "action_null" msgid "action_null"
msgstr "" msgstr ""
#. Default: "Are you sure you want to apply this change?"
msgid "save_confirm"
msgstr ""
#. Default: "Go to top" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "" msgstr ""

View file

@ -247,6 +247,10 @@ msgstr ""
msgid "action_null" msgid "action_null"
msgstr "" msgstr ""
#. Default: "Are you sure you want to apply this change?"
msgid "save_confirm"
msgstr ""
#. Default: "Go to top" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "" msgstr ""

View file

@ -247,6 +247,10 @@ msgstr ""
msgid "action_null" msgid "action_null"
msgstr "" msgstr ""
#. Default: "Are you sure you want to apply this change?"
msgid "save_confirm"
msgstr ""
#. Default: "Go to top" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "Zurück zum Anfang" msgstr "Zurück zum Anfang"

View file

@ -248,6 +248,10 @@ msgstr "Action could not be performed on ${nb} element(s)."
msgid "action_null" msgid "action_null"
msgstr "Action had no effect." 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" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "Go to top" msgstr "Go to top"

View file

@ -247,6 +247,10 @@ msgstr ""
msgid "action_null" msgid "action_null"
msgstr "" msgstr ""
#. Default: "Are you sure you want to apply this change?"
msgid "save_confirm"
msgstr ""
#. Default: "Go to top" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "Ir al inicio" msgstr "Ir al inicio"

View file

@ -248,6 +248,10 @@ msgstr "L'action n'a pu être réalisée pour ${nb} élément(s)."
msgid "action_null" msgid "action_null"
msgstr "L'action n'a eu aucun effet." 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" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "Aller au début" msgstr "Aller au début"

View file

@ -247,6 +247,10 @@ msgstr ""
msgid "action_null" msgid "action_null"
msgstr "" msgstr ""
#. Default: "Are you sure you want to apply this change?"
msgid "save_confirm"
msgstr ""
#. Default: "Go to top" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "Andare all'inizio" msgstr "Andare all'inizio"

View file

@ -247,6 +247,10 @@ msgstr ""
msgid "action_null" msgid "action_null"
msgstr "" msgstr ""
#. Default: "Are you sure you want to apply this change?"
msgid "save_confirm"
msgstr ""
#. Default: "Go to top" #. Default: "Go to top"
msgid "goto_first" msgid "goto_first"
msgstr "Ga naar het begin" msgstr "Ga naar het begin"

View file

@ -62,7 +62,7 @@ img { border: 0; vertical-align: middle }
.userStripText { padding: 0 0.3em 0 0.3em; color: white } .userStripText { padding: 0 0.3em 0 0.3em; color: white }
.userStrip a { color: #e7e7e7 } .userStrip a { color: #e7e7e7 }
.userStrip a:visited { color: #e7e7e7 } .userStrip a:visited { color: #e7e7e7 }
.breadcrumb { font-size: 11pt } .breadcrumb { font-size: 11pt; padding-bottom: 6px }
.login { margin: 3px; color: black } .login { margin: 3px; color: black }
input.button { color: #666666; height: 20px; width: 130px; input.button { color: #666666; height: 20px; width: 130px;
cursor:pointer; font-size: 90%; padding: 1px 0 0 10px; 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% } .dropdownMenu { cursor: pointer; padding-right: 4px; font-size: 93% }
.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: 1px solid grey; .list td, .list th { border: 3px solid #ededed; color: grey;
padding-left: 5px; padding-right: 5px; padding-top: 3px } padding: 3px 5px 0 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: 2px solid grey; padding: 2px 2px } border-bottom: 5px solid #fdfdfd; padding: 3px 5px 0 5px }
.grid td { padding-right: 5px } .grid td { padding: 3px 3px 0 3px }
.cellGap { padding-right: 0.4em } .cellGap { padding-right: 0.4em }
.cellDashed { border: 1px dashed grey !important } .cellDashed { border: 1px dashed grey !important }
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important } .noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important }
@ -127,7 +127,8 @@ td.search { padding-top: 8px }
color: white } color: white }
.even { background-color: #f9f9f9 } .even { background-color: #f9f9f9 }
.odd { background-color: #f4f4f4 } .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% } .by { padding: 5px; color: grey; font-size: 97% }
.underline { border-bottom: 1px dotted grey } .underline { border-bottom: 1px dotted grey }
.state { font-weight: bold; border-bottom: 1px dashed grey } .state { font-weight: bold; border-bottom: 1px dashed grey }

View file

@ -279,7 +279,7 @@ function doInlineSave(objectUid, name, objectUrl, content){
/* Ajax-saves p_content of field named p_name on object whose id is /* 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 p_objectUid and whose URL is p_objectUrl. Asks a confirmation before
doing it. */ doing it. */
var doIt = confirm('Do it?'); var doIt = confirm(save_confirm);
var params = {'action': 'storeFromAjax', 'layoutType': 'view'}; var params = {'action': 'storeFromAjax', 'layoutType': 'view'};
var hook = null; var hook = null;
if (!doIt) { if (!doIt) {

View file

@ -587,6 +587,14 @@ class ToolWrapper(AbstractWrapper):
'''Sends a mail. See doc for appy.gen.mail.sendMail.''' '''Sends a mail. See doc for appy.gen.mail.sendMail.'''
sendMail(self, to, subject, body, attachments=attachments) 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): def refreshCatalog(self, startObject=None):
'''Reindex all Appy objects. For some unknown reason, method '''Reindex all Appy objects. For some unknown reason, method
catalog.refreshCatalog is not able to recatalog Appy objects.''' catalog.refreshCatalog is not able to recatalog Appy objects.'''

View file

@ -329,18 +329,17 @@ class AbstractWrapper(object):
<th align=":dleft">:_('action_comment')</th> <th align=":dleft">:_('action_comment')</th>
</tr> </tr>
<tr for="event in objs" <tr for="event in objs"
var2="odd=loop.event.odd; var2="rhComments=event.get('comments', None);
rhComments=event.get('comments', None);
state=event.get('review_state', None); state=event.get('review_state', None);
action=event['action']; action=event['action'];
isDataChange=action == '_datachange_'" isDataChange=action == '_datachange_'"
class="odd and 'even' or 'odd'" valign="top"> class=":loop.event.odd and 'even' or 'odd'" valign="top">
<td if="isDataChange"> <td if="isDataChange">
<x>:_('data_change')</x> <x>:_('data_change')</x>
<img if="user.has_role('Manager')" class="clickable" <img if="user.has_role('Manager')" class="clickable"
src=":url('delete')" src=":url('delete')"
onclick=":'onDeleteEvent(%s,%s)' % \ onclick=":'onDeleteEvent(%s,%s)' % \
(q(zobj.UID()), q(event['time']))"/> (q(zobj.id), q(event['time']))"/>
</td> </td>
<td if="not isDataChange">:_(zobj.getWorkflowLabel(action))</td> <td if="not isDataChange">:_(zobj.getWorkflowLabel(action))</td>
<td var="actorId=event.get('actor')"> <td var="actorId=event.get('actor')">
@ -408,7 +407,7 @@ class AbstractWrapper(object):
<img class="clickable" onclick="toggleCookie('appyHistory')" <img class="clickable" onclick="toggleCookie('appyHistory')"
src=":historyExpanded and url('collapse.gif') or url('expand.gif')" src=":historyExpanded and url('collapse.gif') or url('expand.gif')"
align=":dleft" id="appyHistory_img" style="padding-right:4px"/> align=":dleft" id="appyHistory_img" style="padding-right:4px"/>
<x>:_('object_history')</x> || <x>:_('object_history')</x> &mdash;
</x> </x>
<!-- Creator and last modification date --> <!-- Creator and last modification date -->
@ -437,7 +436,7 @@ class AbstractWrapper(object):
<td colspan="2"> <td colspan="2">
<span id="appyHistory" <span id="appyHistory"
style=":historyExpanded and 'display:block' or 'display:none'"> 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)' % \ <script type="text/javascript">::'askObjectHistory(%s,%s,%d,0)' % \
(q(ajaxHookId), q(zobj.absolute_url()), \ (q(ajaxHookId), q(zobj.absolute_url()), \
historyMaxPerPage)</script> historyMaxPerPage)</script>
@ -701,9 +700,10 @@ class AbstractWrapper(object):
elif name == 'session': return self.o.REQUEST.SESSION elif name == 'session': return self.o.REQUEST.SESSION
elif name == 'typeName': return self.__class__.__bases__[-1].__name__ elif name == 'typeName': return self.__class__.__bases__[-1].__name__
elif name == 'id': return self.o.id 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 == 'klass': return self.__class__.__bases__[-1]
elif name == 'created': return self.o.created elif name == 'created': return self.o.created
elif name == 'creator': return self.o.creator
elif name == 'modified': return self.o.modified elif name == 'modified': return self.o.modified
elif name == 'url': return self.o.absolute_url() elif name == 'url': return self.o.absolute_url()
elif name == 'state': return self.o.State() elif name == 'state': return self.o.State()
@ -751,6 +751,14 @@ class AbstractWrapper(object):
if custom: return custom(self, *args, **kwargs) if custom: return custom(self, *args, **kwargs)
def getField(self, name): return self.o.getAppyType(name) 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): def isEmpty(self, name):
'''Returns True if value of field p_name is considered as being '''Returns True if value of field p_name is considered as being
empty.''' empty.'''
@ -1004,7 +1012,7 @@ class AbstractWrapper(object):
# Determine where to put the result # Determine where to put the result
toDisk = (at != 'string') toDisk = (at != 'string')
if toDisk and not at: if toDisk and not at:
at = getOsTempFolder() + '/' + self.o.UID() + '.xml' at = getOsTempFolder() + '/' + self.o.id + '.xml'
# Create the XML version of the object # Create the XML version of the object
marshaller = XmlMarshaller(cdata=True, dumpUnicode=True, marshaller = XmlMarshaller(cdata=True, dumpUnicode=True,
dumpXmlPrologue=toDisk, dumpXmlPrologue=toDisk,
@ -1036,7 +1044,7 @@ class AbstractWrapper(object):
whose values are the previous field values.''' whose values are the previous field values.'''
self.o.addDataChange(data) 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 '''Gets, from the object history, the last occurrence of transition
named p_transition. p_transition can be a list of names: in this named p_transition. p_transition can be a list of names: in this
case, it returns the most recent occurrence of those transitions. If 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 if getattr(workflow, name).__class__.__name__ != 'State': continue
res.append((name, o.translate(o.getWorkflowLabel(name)))) res.append((name, o.translate(o.getWorkflowLabel(name))))
return res 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)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -288,17 +288,18 @@ class Renderer:
supposed to be in binary format in p_content. The document supposed to be in binary format in p_content. The document
p_format may be: odt or any format in imageFormats. 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; * p_anchor defines the way the image is anchored into the document;
Valid values are 'page','paragraph', 'char' and 'as-char'; Valid values are 'page','paragraph', 'char' and 'as-char';
* p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p' * p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p'
tag; tag;
* p_size, if specified, is a tuple of float or integers * p_size, if specified, is a tuple of float or integers
(width, height) expressing size in p_sizeUnit (see below). (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" * p_sizeUnit is the unit for p_size elements, it can be "cm"
(centimeters) or "px" (pixels). (centimeters) or "px" (pixels);
* If p_style is given, it is the content of a "style" attribute, * if p_style is given, it is the content of a "style" attribute,
containing CSS attributes. If "width" and "heigth" attributes are containing CSS attributes. If "width" and "heigth" attributes are
found there, they will override p_size and p_sizeUnit. found there, they will override p_size and p_sizeUnit.
@ -308,8 +309,7 @@ class Renderer:
''' '''
importer = None importer = None
# Is there someting to import? # Is there someting to import?
if not content and not at: if not content and not at: raise PodError(DOC_NOT_SPECIFIED)
raise PodError(DOC_NOT_SPECIFIED)
# Convert Zope files into Appy wrappers. # Convert Zope files into Appy wrappers.
if content.__class__.__name__ in ('File', 'Image'): if content.__class__.__name__ in ('File', 'Image'):
content = FileWrapper(content) content = FileWrapper(content)