From 7484fbca934c56f03c42d5ea0aa789e983a11548 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Tue, 21 Oct 2014 09:25:37 +0200 Subject: [PATCH] [gen] Added the possibility to navigate to tied object number X within a list of tied objects from a Ref that is numbered. --- fields/ref.py | 32 ++++-- fields/search.py | 9 ++ gen/layout.py | 2 +- gen/mixins/ToolMixin.py | 148 ++---------------------- gen/mixins/__init__.py | 20 ++-- gen/navigate.py | 220 ++++++++++++++++++++++++++++++++++++ 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.js | 13 +++ gen/ui/gotoNumber.png | Bin 0 -> 235 bytes gen/wrappers/ToolWrapper.py | 78 ++++++------- gen/wrappers/__init__.py | 45 +++----- 18 files changed, 371 insertions(+), 228 deletions(-) create mode 100644 gen/navigate.py create mode 100644 gen/ui/gotoNumber.png diff --git a/fields/ref.py b/fields/ref.py index cc7c95a..7e752fb 100644 --- a/fields/ref.py +++ b/fields/ref.py @@ -38,9 +38,8 @@ class Ref(Field): # defined. If we are on a forward reference, the "nav" parameter is added to # the URL for allowing to navigate from one object to the next/previous one. pxObjectTitle = Px(''' - ::tied.o.getSupTitle(navInfo) ::tied.o.getListTitle(nav=navInfo, target=target, page=pageName, \ @@ -105,8 +104,8 @@ class Ref(Field): - + value=":field.getNavInfo(zobj, 0, totalNumber)"/> -1) and (res.totalNumber > previousIndex): - previousNeeded = True - nextNeeded = False # Next ? - nextIndex = res.currentNumber - if nextIndex < res.totalNumber: nextNeeded = True - firstNeeded = False # First ? - firstIndex = 0 - if previousIndex > 0: firstNeeded = True - lastNeeded = False # Last ? - lastIndex = res.totalNumber - 1 - if (nextIndex < lastIndex): lastNeeded = True - # Get the list of available UIDs surrounding the current object - if t == 'ref': # Manage navigation from a reference - # In the case of a reference, we retrieve ALL surrounding objects. - masterObj = self.getObject(d1) - batchSize = masterObj.getAppyType(fieldName).maxPerPage - uids = getattr(masterObj, fieldName) - # Display the reference widget at the page where the current object - # lies. - startNumberKey = '%s%s_startNumber' % (masterObj.id, fieldName) - startNumber = self.computeStartNumberFrom(res.currentNumber-1, - res.totalNumber, batchSize) - res.sourceUrl = masterObj.getUrl(**{startNumberKey:startNumber, - 'page':pageName, 'nav':''}) - else: # Manage navigation from a search - contentType = d1 - searchName = keySuffix = d2 - batchSize = self.appy().numberOfResultsPerPage - if not searchName: keySuffix = contentType - s = rq.SESSION - searchKey = 'search_%s' % keySuffix - if s.has_key(searchKey): uids = s[searchKey] - else: uids = {} - # In the case of a search, we retrieve only a part of all - # surrounding objects, those that are stored in the session. - if (previousNeeded and not uids.has_key(previousIndex)) or \ - (nextNeeded and not uids.has_key(nextIndex)): - # I do not have this UID in session. I will need to - # retrigger the query by querying all objects surrounding - # this one. - newStartNumber = (res.currentNumber-1) - (batchSize / 2) - if newStartNumber < 0: newStartNumber = 0 - self.executeQuery(contentType, searchName=searchName, - startNumber=newStartNumber, remember=True) - uids = s[searchKey] - # For the moment, for first and last, we get them only if we have - # them in session. - if not uids.has_key(0): firstNeeded = False - if not uids.has_key(lastIndex): lastNeeded = False - # Compute URL of source object - startNumber = self.computeStartNumberFrom(res.currentNumber-1, - res.totalNumber, batchSize) - res.sourceUrl = self.getQueryUrl(contentType, searchName, - startNumber=startNumber) - # Compute URLs - for urlType in ('previous', 'next', 'first', 'last'): - exec 'needIt = %sNeeded' % urlType - urlKey = '%sUrl' % urlType - setattr(res, urlKey, None) - if needIt: - exec 'index = %sIndex' % urlType - uid = None - try: - uid = uids[index] - # uids can be a list (ref) or a dict (search) - except KeyError: pass - except IndexError: pass - if uid: - brain = self.getObject(uid, brain=True) - if brain: - sibling = brain.getObject() - setattr(res, urlKey, sibling.getUrl(\ - nav=newNav % (index + 1), - page=rq.get('page', 'main'), inPopup=inPopup)) - return res + def getNavigationInfo(self, nav, inPopup): + '''Produces a Siblings instance from navigation info p_nav.''' + return Siblings.get(nav, self, inPopup) def getGroupedSearchFields(self, searchInfo): '''This method transforms p_searchInfo.fields, which is a "flat" diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index 24d2e44..849aa6e 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -35,20 +35,17 @@ class BaseMixin: def getInitiatorInfo(self, appy=False): '''Gets information about a potential initiator object from the request. - Returns a 3-tuple (initiator, pageName, field): + Returns a 2-tuple (initiator, field): * initiator is the initiator (Zope or Appy) object; - * pageName is the page on the initiator where the origin of the Ref - field lies; * field is the Ref instance. ''' rq = self.REQUEST - if not rq.get('nav', '').startswith('ref.'): return None, None, None + if not rq.get('nav', '').startswith('ref.'): return None, None splitted = rq['nav'].split('.') initiator = self.getTool().getObject(splitted[1]) - fieldName, page = splitted[2].split(':') - field = initiator.getAppyType(fieldName) + field = initiator.getAppyType(splitted[2]) if appy: initiator = initiator.appy() - return initiator, page, field + return initiator, field def createOrUpdate(self, created, values, initiator=None, initiatorField=None): @@ -203,7 +200,7 @@ class BaseMixin: # create the object. urlParams = {'mode':'edit', 'page':'main', 'nav':'', 'inPopup':rq.get('popup') == '1'} - initiator, initiatorPage, initiatorField = self.getInitiatorInfo() + initiator, initiatorField = self.getInitiatorInfo() if initiator: # The object to create will be linked to an initiator object through # a Ref field. We create here a new navigation string with one more @@ -387,7 +384,8 @@ class BaseMixin: isNew = self.isTemporary() inPopup = rq.get('popup') == '1' # If this object is created from an initiator, get info about him. - initiator, initiatorPage, initiatorField = self.getInitiatorInfo() + initiator, initiatorField = self.getInitiatorInfo() + initiatorPage = initiatorField and initiatorField.pageName or None # If the user clicked on 'Cancel', go back to the previous page. buttonClicked = rq.get('button') if buttonClicked == 'cancel': @@ -696,6 +694,10 @@ class BaseMixin: self.REQUEST.set(field.name, '') return self.edit() + def gotoTied(self): + '''Redirects the user to an object tied to this one.''' + return self.getAppyType(self.REQUEST['field']).onGotoTied(self) + def getCreateFolder(self): '''When an object must be created from this one through a Ref field, we must know where to put the newly create object: within this one if it diff --git a/gen/navigate.py b/gen/navigate.py new file mode 100644 index 0000000..a36e88c --- /dev/null +++ b/gen/navigate.py @@ -0,0 +1,220 @@ +# ------------------------------------------------------------------------------ +from appy.px import Px + +# ------------------------------------------------------------------------------ +class Siblings: + '''Abstract class containing information for navigating from one object to + its siblings.''' + siblingTypes = ('previous', 'next', 'first', 'last') + + # Buttons for going to siblings of the current object. + pxNavigate = Px(''' + + + + + + + + + :self.number // + :self.total + + + + + +
:obj.pxGotoNumber
''') + + @staticmethod + def get(nav, tool, inPopup): + '''This method analyses the navigation info p_nav and returns the + corresponding concrete Siblings instance.''' + elems = nav.split('.') + params = elems[1:] + if elems[0] == 'ref': return RefSiblings(tool, inPopup, *params) + elif elems[0] == 'search': return SearchSiblings(tool, inPopup, *params) + + def computeStartNumber(self): + '''Returns the start number of the batch where the current element + lies.''' + # First index starts at O, so we calibrate self.number + number = self.number - 1 + batchSize = self.getBatchSize() + res = 0 + while (res < self.total): + if (number < res + batchSize): return res + res += batchSize + return res + + def __init__(self, tool, inPopup, number, total): + self.tool = tool + self.request = tool.REQUEST + # Are we in a popup window or not? + self.inPopup = inPopup + # The number of the current element + self.number = int(number) + # The total number of siblings + self.total = int(total) + # Do I need to navigate to first, previous, next and/or last sibling ? + self.previousNeeded = False # Previous ? + self.previousIndex = self.number - 2 + if (self.previousIndex > -1) and (self.total > self.previousIndex): + self.previousNeeded = True + self.nextNeeded = False # Next ? + self.nextIndex = self.number + if self.nextIndex < self.total: self.nextNeeded = True + self.firstNeeded = False # First ? + self.firstIndex = 0 + if self.previousIndex > 0: self.firstNeeded = True + self.lastNeeded = False # Last ? + self.lastIndex = self.total - 1 + if (self.nextIndex < self.lastIndex): self.lastNeeded = True + # Compute the UIDs of the siblings of the current object + self.siblings = self.getSiblings() + # Compute back URL and URLs to siblings + self.sourceUrl = self.getSourceUrl() + siblingNav = self.getNavKey() + siblingPage = self.request.get('page', 'main') + for urlType in self.siblingTypes: + exec 'needIt = self.%sNeeded' % urlType + urlKey = '%sUrl' % urlType + setattr(self, urlKey, None) + if not needIt: continue + exec 'index = self.%sIndex' % urlType + uid = None + try: + # self.siblings can be a list (ref) or a dict (search) + uid = self.siblings[index] + except KeyError: continue + except IndexError: continue + if not uid: continue + sibling = self.tool.getObject(uid) + if not sibling: continue + setattr(self, urlKey, sibling.getUrl(nav=siblingNav % (index + 1), + page=siblingPage, inPopup=inPopup)) + +# ------------------------------------------------------------------------------ +class RefSiblings(Siblings): + '''Class containing information for navigating from one object to another + within tied objects from a Ref field.''' + prefix = 'ref' + + def __init__(self, tool, inPopup, sourceUid, fieldName, number, total): + # The source object of the Ref field + self.sourceObject = tool.getObject(sourceUid) + # The Ref field in itself + self.field = self.sourceObject.getAppyType(fieldName) + # Call the base constructor + Siblings.__init__(self, tool, inPopup, number, total) + + def getNavKey(self): + '''Returns the general navigation key for navigating to another + sibling.''' + return self.field.getNavInfo(self.sourceObject, None, self.total) + + def getBackText(self): + '''Computes the text to display when the user want to navigate back to + the list of tied objects.''' + _ = self.tool.translate + return '%s - %s' % (self.sourceObject.Title(), _(self.field.labelId)) + + def getBatchSize(self): + '''Returns the maximum number of shown objects at a time for this + ref.''' + return self.field.maxPerPage + + def getSiblings(self): + '''Returns the siblings of the current object.''' + return getattr(self.sourceObject, self.field.name, ()) + + def getSourceUrl(self): + '''Computes the URL allowing to go back to self.sourceObject's page + where self.field lies and shows the list of tied objects, at the + batch where the current object lies.''' + # Allow to go back to the batch where the current object lies + field = self.field + startNumberKey = '%s_%s_objs_startNumber' % \ + (self.sourceObject.id,field.name) + startNumber = str(self.computeStartNumber()) + return self.sourceObject.getUrl(**{startNumberKey:startNumber, + 'page':field.pageName, 'nav':''}) + + def showGotoNumber(self): + '''Show "goto number" if the Ref field is numbered.''' + return self.field.isNumbered(self.sourceObject) + +# ------------------------------------------------------------------------------ +class SearchSiblings(Siblings): + '''Class containing information for navigating from one object to another + within results of a search.''' + prefix = 'search' + + def __init__(self, tool, inPopup, className, searchName, number, total): + # The class determining the type of searched objects + self.className = className + # Get the search object + self.searchName = searchName + self.uiSearch = tool.getSearch(className, searchName, ui=True) + self.search = self.uiSearch.search + Siblings.__init__(self, tool, inPopup, number, total) + + def getNavKey(self): + '''Returns the general navigation key for navigating to another + sibling.''' + return 'search.%s.%s.%%d.%d' % (self.className, self.searchName, + self.total) + + def getBackText(self): + '''Computes the text to display when the user want to navigate back to + the list of searched objects.''' + return self.uiSearch.translated + + def getBatchSize(self): + '''Returns the maximum number of shown objects at a time for this + search.''' + return self.search.maxPerPage + + def getSiblings(self): + '''Returns the siblings of the current object. For performance reasons, + only a part of the is stored, in the session object.''' + session = self.request.SESSION + searchKey = self.search.getSessionKey(self.className) + if session.has_key(searchKey): res = session[searchKey] + else: res = {} + if (self.previousNeeded and not res.has_key(self.previousIndex)) or \ + (self.nextNeeded and not res.has_key(self.nextIndex)): + # The needed sibling UID is not in session. We will need to + # retrigger the query by querying all objects surrounding this one. + newStartNumber = (self.number-1) - (self.search.maxPerPage / 2) + if newStartNumber < 0: newStartNumber = 0 + self.tool.executeQuery(self.className, search=self.search, + startNumber=newStartNumber, remember=True) + res = session[searchKey] + # For the moment, for first and last, we get them only if we have them + # in session. + if not res.has_key(0): self.firstNeeded = False + if not res.has_key(self.lastIndex): self.lastNeeded = False + return res + + def getSourceUrl(self): + '''Computes the (non-Ajax) URL allowing to go back to the search + results, at the batch where the current object lies.''' + params = 'className=%s&search=%s&startNumber=%d' % \ + (self.className, self.searchName, self.computeStartNumber()) + ref = self.request.get('ref', None) + if ref: params += '&ref=%s' % ref + return '%s/query?%s' % (self.tool.absolute_url(), params) + + def showGotoNumber(self): return +# ------------------------------------------------------------------------------ diff --git a/gen/tr/Appy.pot b/gen/tr/Appy.pot index 71a0d32..6637294 100644 --- a/gen/tr/Appy.pot +++ b/gen/tr/Appy.pot @@ -275,6 +275,10 @@ msgstr "" msgid "goto_source" msgstr "" +#. Default: "Go to number" +msgid "goto_number" +msgstr "" + #. Default: "Whatever" msgid "whatever" msgstr "" diff --git a/gen/tr/ar.po b/gen/tr/ar.po index 488977d..497d0d9 100644 --- a/gen/tr/ar.po +++ b/gen/tr/ar.po @@ -275,6 +275,10 @@ msgstr "" msgid "goto_source" msgstr "" +#. Default: "Go to number" +msgid "goto_number" +msgstr "" + #. Default: "Whatever" msgid "whatever" msgstr "" diff --git a/gen/tr/de.po b/gen/tr/de.po index 9b63a21..96c134c 100644 --- a/gen/tr/de.po +++ b/gen/tr/de.po @@ -275,6 +275,10 @@ msgstr "Gehen Sie zum Ende" msgid "goto_source" msgstr "Zurück" +#. Default: "Go to number" +msgid "goto_number" +msgstr "" + #. Default: "Whatever" msgid "whatever" msgstr "Ohne Bedeutung" diff --git a/gen/tr/en.po b/gen/tr/en.po index 9be4290..3075f4d 100644 --- a/gen/tr/en.po +++ b/gen/tr/en.po @@ -276,6 +276,10 @@ msgstr "Go to end" msgid "goto_source" msgstr "Go back" +#. Default: "Go to number" +msgid "goto_number" +msgstr "Go to number" + #. Default: "Whatever" msgid "whatever" msgstr "Whatever" diff --git a/gen/tr/es.po b/gen/tr/es.po index 055c8c4..c35c59b 100644 --- a/gen/tr/es.po +++ b/gen/tr/es.po @@ -275,6 +275,10 @@ msgstr "Ir al final" msgid "goto_source" msgstr "Volver" +#. Default: "Go to number" +msgid "goto_number" +msgstr "" + #. Default: "Whatever" msgid "whatever" msgstr "No importa" diff --git a/gen/tr/fr.po b/gen/tr/fr.po index 038a0bd..f18902a 100644 --- a/gen/tr/fr.po +++ b/gen/tr/fr.po @@ -276,6 +276,10 @@ msgstr "Aller à la fin" msgid "goto_source" msgstr "Retour" +#. Default: "Go to number" +msgid "goto_number" +msgstr "Aller au numéro" + #. Default: "Whatever" msgid "whatever" msgstr "Peu importe" diff --git a/gen/tr/it.po b/gen/tr/it.po index 98e2fd1..1868984 100644 --- a/gen/tr/it.po +++ b/gen/tr/it.po @@ -275,6 +275,10 @@ msgstr "Andare alla fine" msgid "goto_source" msgstr "Andare indietro" +#. Default: "Go to number" +msgid "goto_number" +msgstr "" + #. Default: "Whatever" msgid "whatever" msgstr "Qualunque" diff --git a/gen/tr/nl.po b/gen/tr/nl.po index f586d2e..d044d9f 100644 --- a/gen/tr/nl.po +++ b/gen/tr/nl.po @@ -275,6 +275,10 @@ msgstr "Ga naar het einde" msgid "goto_source" msgstr "Terug" +#. Default: "Go to number" +msgid "goto_number" +msgstr "" + #. Default: "Whatever" msgid "whatever" msgstr "Maakt niets uit" diff --git a/gen/ui/appy.js b/gen/ui/appy.js index a86842f..c17eca4 100644 --- a/gen/ui/appy.js +++ b/gen/ui/appy.js @@ -282,6 +282,19 @@ function askRefField(hookId, objectUrl, innerRef, startNumber, action, evalInnerScripts); } +function gotoTied(objectUrl, field, numberWidget, total) { + // Check that the number is correct + try { + var number = parseInt(numberWidget.value); + if (!isNaN(number)) { + if ((number >= 1) && (number <= total)) { + goto(objectUrl + '/gotoTied?field=' + field + '&number=' + number); + } + else numberWidget.style.background = wrongTextInput; } + else numberWidget.style.background = wrongTextInput; } + catch (err) { numberWidget.style.background = wrongTextInput; } +} + function askField(hookId, objectUrl, layoutType, customParams, showChanges, masterValues, requestValue, error, className){ // Sends an Ajax request for getting the content of any field. diff --git a/gen/ui/gotoNumber.png b/gen/ui/gotoNumber.png new file mode 100644 index 0000000000000000000000000000000000000000..4177786cf2c666a3870ee93affbcd85fd50d85c1 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~k#0(_eTwE3cDaPU;cPEB*=VV@jWCDCbTp1YB z|2H@Q(T8T!e;^elL4Lsu4$p3+fjCLt?k)@+tg;>;{XE z)7O>#9+x1uIyd|F>J>mC*%H@?66gHf+|;}h2Ir#G#FEq$h4Rdj426)4R0VfW-+=ci zs(C<#s-7;6Asp9}6FfW;5)wK(7=)6N9GbRnZES2bG-N!*)ZLic_==%*1sngx^5tuR P8W=oX{an^LB{Ts5!8$+< literal 0 HcmV?d00001 diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py index 6838b1b..e59767a 100644 --- a/gen/wrappers/ToolWrapper.py +++ b/gen/wrappers/ToolWrapper.py @@ -47,51 +47,47 @@ class ToolWrapper(AbstractWrapper): # Buttons for navigating among a list of objects (from a Ref field or a # query): next,back,first,last... pxNavigate = Px(''' -
- - - - + + + - - + + - - + + + :startNumber + 1 + :startNumber + batchNumber // + :totalNumber + - - + + - - - -
- :startNumber + 1 - :startNumber + batchNumber // - :totalNumber
+ + + + + :obj.pxGotoNumber
''') # -------------------------------------------------------------------------- @@ -508,7 +504,7 @@ class ToolWrapper(AbstractWrapper): :uiSearch.translatedDescr
- :tool.pxNavigate + :tool.pxNavigate diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py index fdf637d..44a438e 100644 --- a/gen/wrappers/__init__.py +++ b/gen/wrappers/__init__.py @@ -19,34 +19,19 @@ class AbstractWrapper(object): '''Any real Appy-managed Zope object has a companion object that is an instance of this class.''' - # Buttons for going to next/previous objects if this one is among bunch of - # referenced or searched objects. currentNumber starts with 1. - pxNavigateSiblings = Px(''' -
- - - - - - - - - :ni.currentNumber // - :ni.totalNumber - - - -
''') + # Input field for going to element number ... + pxGotoNumber = Px(''' + + :label + ''') pxNavigationStrip = Px(''' @@ -66,7 +51,9 @@ class AbstractWrapper(object): ::sub - +
:obj.pxNavigateSiblings:self.pxNavigate