From 1d0ee7a614be0ede6736b995f410215027e4d367 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Fri, 2 May 2014 12:35:09 +0200 Subject: [PATCH] [gen] Bugfix in the Ref field; added method workflow.Transition.getBack that finds the 'back' transition of a given transition. --- fields/group.py | 3 +- fields/ref.py | 149 ++++++++++++++++++------------------ fields/workflow.py | 24 ++++++ gen/mixins/ToolMixin.py | 46 +++++------ gen/mixins/__init__.py | 11 ++- gen/model.py | 2 +- gen/tr/Appy.pot | 4 + gen/tr/ar.po | 4 + gen/tr/de.po | 4 + gen/tr/en.po | 4 + gen/tr/es.po | 4 + gen/tr/fr.po | 4 + gen/tr/it.po | 4 + gen/tr/nl.po | 4 + gen/ui/appy.css | 13 ++-- gen/ui/appy.js | 2 +- gen/wrappers/ToolWrapper.py | 8 ++ gen/wrappers/__init__.py | 32 +++++--- pod/renderer.py | 12 +-- 19 files changed, 206 insertions(+), 128 deletions(-) diff --git a/fields/group.py b/fields/group.py index 36360ba..828a5be 100644 --- a/fields/group.py +++ b/fields/group.py @@ -219,8 +219,7 @@ class UiGroup: - :field.pxView diff --git a/fields/ref.py b/fields/ref.py index a874b3d..03d1b8b 100644 --- a/fields/ref.py +++ b/fields/ref.py @@ -195,86 +195,85 @@ class Ref(Field): # PX that displays referred objects as a list. pxViewList = Px(''' - -
:_('no_ref')
+
+ :_(subLabel) + (:totalNumber) + :field.pxAdd + + +
- -
- :_(subLabel) - (:totalNumber) - :field.pxAdd - - -
+ + :tool.pxNavigate - - :tool.pxNavigate + +

:_('no_ref')

- - - - - - - - - - - - - -
- :_(refField.labelId) - :field.pxSortIcons - :tool.pxShowDetails - - -
:field.pxNumber - - - :field.pxObjectTitle -
:field.pxObjectActions
-
- - - :field.pxRender - -
- -
+ + + + + + + + + + + + + +
+ :_(refField.labelId) + :field.pxSortIcons + :tool.pxShowDetails + + +
:field.pxNumber + + + :field.pxObjectTitle +
:field.pxObjectActions
+
+ + + :field.pxRender + +
+ +
- -
:field.pxGlobalActions
+ +
:field.pxGlobalActions
- - :tool.pxNavigate + + :tool.pxNavigate - - -
''') + + ''') # 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) + title=":title">:ztool.truncateValue(title, field.width) ''') pxSearch = Px(''' diff --git a/fields/workflow.py b/fields/workflow.py index f450eb8..bb6f641 100644 --- a/fields/workflow.py +++ b/fields/workflow.py @@ -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('''%s' % \ - (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 '%s...' % (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): diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index e2c5c28..2ce4af5 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -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): diff --git a/gen/model.py b/gen/model.py index da339b2..f994357 100644 --- a/gen/model.py +++ b/gen/model.py @@ -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 diff --git a/gen/tr/Appy.pot b/gen/tr/Appy.pot index fc90a10..f89465d 100644 --- a/gen/tr/Appy.pot +++ b/gen/tr/Appy.pot @@ -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 "" diff --git a/gen/tr/ar.po b/gen/tr/ar.po index db5a41f..c6197b3 100644 --- a/gen/tr/ar.po +++ b/gen/tr/ar.po @@ -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 "" diff --git a/gen/tr/de.po b/gen/tr/de.po index d09feaa..c5cbb7d 100644 --- a/gen/tr/de.po +++ b/gen/tr/de.po @@ -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" diff --git a/gen/tr/en.po b/gen/tr/en.po index a3d8794..813bb1f 100644 --- a/gen/tr/en.po +++ b/gen/tr/en.po @@ -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" diff --git a/gen/tr/es.po b/gen/tr/es.po index 1eb6ee1..f49e40a 100644 --- a/gen/tr/es.po +++ b/gen/tr/es.po @@ -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" diff --git a/gen/tr/fr.po b/gen/tr/fr.po index bae3355..6e47ab8 100644 --- a/gen/tr/fr.po +++ b/gen/tr/fr.po @@ -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" diff --git a/gen/tr/it.po b/gen/tr/it.po index 5c1305e..b9cee8c 100644 --- a/gen/tr/it.po +++ b/gen/tr/it.po @@ -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" diff --git a/gen/tr/nl.po b/gen/tr/nl.po index b549eb1..fd00039 100644 --- a/gen/tr/nl.po +++ b/gen/tr/nl.po @@ -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" diff --git a/gen/ui/appy.css b/gen/ui/appy.css index 38347b6..abe00b2 100644 --- a/gen/ui/appy.css +++ b/gen/ui/appy.css @@ -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 } diff --git a/gen/ui/appy.js b/gen/ui/appy.js index ee3be6b..835cc3e 100644 --- a/gen/ui/appy.js +++ b/gen/ui/appy.js @@ -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) { diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py index dcc0bde..b4ea2da 100644 --- a/gen/wrappers/ToolWrapper.py +++ b/gen/wrappers/ToolWrapper.py @@ -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.''' diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py index ebd5075..e397967 100644 --- a/gen/wrappers/__init__.py +++ b/gen/wrappers/__init__.py @@ -329,18 +329,17 @@ class AbstractWrapper(object): :_('action_comment') + class=":loop.event.odd and 'even' or 'odd'" valign="top"> :_('data_change') + (q(zobj.id), q(event['time']))"/> :_(zobj.getWorkflowLabel(action)) @@ -408,7 +407,7 @@ class AbstractWrapper(object): - :_('object_history') || + :_('object_history') @@ -437,7 +436,7 @@ class AbstractWrapper(object): -
+
@@ -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) # ------------------------------------------------------------------------------ diff --git a/pod/renderer.py b/pod/renderer.py index a6f5120..6117ab6 100644 --- a/pod/renderer.py +++ b/pod/renderer.py @@ -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)