From de97208137e75cde3c077c2aa70c2b5e1be02f27 Mon Sep 17 00:00:00 2001
From: Julien Muchembled <jm@nexedi.com>
Date: Thu, 27 Oct 2016 16:07:07 +0200
Subject: [PATCH] listbox: sorting by CAST type (float)

Only 'float' supported for the moment. This should cover the case of integers.

Example of 'Sortable Columns' value in a ListBox:

  id | float
  title |

Since there's a fallback to 'Searchable Columns' when 'Sortable Columns' is
empty, the cast type can also be specified there, in order to avoid duplication.
---
 .../Folder_viewSortOnDialog/sort_type.xml     | 16 +----
 product/ERP5Form/ListBox.py                   | 59 ++++++++++-------
 product/ERP5Form/Tool/SelectionTool.py        | 65 +++++++++----------
 3 files changed, 72 insertions(+), 68 deletions(-)

diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Folder_viewSortOnDialog/sort_type.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Folder_viewSortOnDialog/sort_type.xml
index 14495d6602..111e71432c 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Folder_viewSortOnDialog/sort_type.xml
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Folder_viewSortOnDialog/sort_type.xml
@@ -237,13 +237,9 @@
                           <string>Default</string>
                           <string>default</string>
                         </tuple>
-                        <tuple>
-                          <string>Alphabetical</string>
-                          <string>CHAR</string>
-                        </tuple>
                         <tuple>
                           <string>Numerical</string>
-                          <string>SIGNED</string>
+                          <string>float</string>
                         </tuple>
                       </list>
                     </value>
@@ -276,19 +272,13 @@
   </record>
   <record id="2" aka="AAAAAAAAAAI=">
     <pickle>
-      <tuple>
-        <tuple>
-          <string>Products.Formulator.TALESField</string>
-          <string>TALESMethod</string>
-        </tuple>
-        <none/>
-      </tuple>
+      <global name="TALESMethod" module="Products.Formulator.TALESField"/>
     </pickle>
     <pickle>
       <dictionary>
         <item>
             <key> <string>_text</string> </key>
-            <value> <string>python: [(here.Base_translateString(\'Default Sort\'), \'\'),(here.Base_translateString(\'Alphabetical\'), \'CHAR\'), (here.Base_translateString(\'Numerical\'), \'SIGNED\')]</string> </value>
+            <value> <string>python: [(here.Base_translateString(k), v) for k,v in field.get_orig_value("sort_columns")]</string> </value>
         </item>
       </dictionary>
     </pickle>
diff --git a/product/ERP5Form/ListBox.py b/product/ERP5Form/ListBox.py
index 04643ee625..0c8526c759 100644
--- a/product/ERP5Form/ListBox.py
+++ b/product/ERP5Form/ListBox.py
@@ -956,23 +956,37 @@ class ListBoxRenderer:
   getPageNavigationMode = getPageNavigationTemplate
 
   @lazyMethod
-  def getSearchColumnIdSet(self):
-    """Return the set of the ids of the search columns. Fall back to the catalog schema, if not defined.
+  def getSearchColumnDict(self):
+    """Return search columns
+
+    The dict values are useful when getSortColumnDict falls back on this value.
+    Fall back to the catalog schema, if not defined.
     """
     search_columns = self.field.get_value('search_columns')
     if search_columns:
-      return {c[0] for c in search_columns}
+      return dict(search_columns)
     isValidColumn = self.getCatalogTool().getSQLCatalog().isValidColumn
-    return {id for id, title in self.getAllColumnList() if isValidColumn(id)}
+    return {id: '' for id, _ in self.getAllColumnList() if isValidColumn(id)}
 
   @lazyMethod
-  def getSortColumnIdSet(self):
-    """Return the set of the ids of the sort columns. Fall back to search column ids, if not defined.
+  def getSortColumnDict(self):
+    """Return sort columns with their cast types as dict values
+
+    Cast types are prefixed by ':' for convenience.
+    Fall back to search columns, if not defined.
     """
-    sort_columns = self.field.get_value('sort_columns')
-    if sort_columns:
-      return {c[0] for c in sort_columns}
-    return self.getSearchColumnIdSet()
+    sort_dict = {}
+    for c, cast in (self.field.get_value('sort_columns') or
+                    self.getSearchColumnDict().iteritems()):
+      if cast == 'float':
+        sort_dict[c] = ':' + cast
+      else:
+        if cast:
+          warn('Each line of the "Sortable Columns" field property must be'
+                ' in the form "<column_id> | <cast_type>", where <cast_type>'
+                " is one of ('', 'float').", DeprecationWarning)
+        sort_dict[c] = ''
+    return sort_dict
 
   @lazyMethod
   def getEditableColumnIdSet(self):
@@ -1034,8 +1048,8 @@ class ListBoxRenderer:
       selection.edit(default_sort_on = self.getDefaultSortColumnList())
 
       # Filter out non-sortable items.
-      sort_column_id_set = self.getSortColumnIdSet()
-      sort_list = [c for c in selection.sort_on if c[0] in sort_column_id_set]
+      sort_column_dict = self.getSortColumnDict()
+      sort_list = [c for c in selection.sort_on if c[0] in sort_column_dict]
       if len(selection.sort_on) != len(sort_list):
         selection.sort_on = sort_list
 
@@ -1557,17 +1571,18 @@ class ListBoxRenderer:
     set to None, otherwise to a string.
     """
     sort_list = self.getSelectionTool().getSelectionSortOrder(self.getSelectionName())
-    sort_dict = {}
-    for sort_item in sort_list:
-      sort_dict[sort_item[0]] = sort_item[1] # sort_item can be couple or a triplet
-    sort_column_id_set = self.getSortColumnIdSet()
+    # sort_item can be couple or a triplet
+    sort_dict = {sort_item[0]: sort_item[1] for sort_item in sort_list}
+    sort_column_dict = self.getSortColumnDict()
 
     value_list = []
     for c in self.getSelectedColumnList():
-      if c[0] in sort_column_id_set:
-        value_list.append((c[0], c[1], sort_dict.get(c[0])))
-      else:
+      column_id = c[0]
+      as_type = sort_column_dict.get(column_id)
+      if as_type is None:
         value_list.append((None, c[1], None))
+      else:
+        value_list.append((column_id + as_type, c[1], sort_dict.get(column_id)))
 
     return value_list
 
@@ -1576,7 +1591,7 @@ class ListBoxRenderer:
     If a column is not searchable, the alias is set to None, otherwise to a string. If a search field is not present,
     it is set to None.
     """
-    search_column_id_set = self.getSearchColumnIdSet()
+    search_column_id_set = self.getSearchColumnDict()
     if param_dict is None:
       param_dict = self.getParamDict()
 
@@ -2575,8 +2590,8 @@ class ListBoxHTMLRenderer(ListBoxRenderer):
       for original_listbox_argument in listbox_arguments_list:
         listbox_argument = original_listbox_argument.replace('%s_' %field_id, '', 1)
         listbox_argument_value = form_dict.get(original_listbox_argument, None)
-        if listbox_argument in list(self.getSearchColumnIdSet()) and \
-           listbox_argument_value not in (None,):
+        if listbox_argument in self.getSearchColumnDict() and \
+           listbox_argument_value is not None:
           update_selection = True
           listbox_kw[listbox_argument] = listbox_argument_value
       if update_selection:
diff --git a/product/ERP5Form/Tool/SelectionTool.py b/product/ERP5Form/Tool/SelectionTool.py
index 353d596c7e..e76542c3a7 100644
--- a/product/ERP5Form/Tool/SelectionTool.py
+++ b/product/ERP5Form/Tool/SelectionTool.py
@@ -553,17 +553,26 @@ class SelectionTool( BaseTool, SimpleItem ):
         listbox_id, sort_on = form["setSelectionQuickSortOrder"].split(".", 1)
 
       # Sort order can be specified in sort_on.
-      forced_sort_order = None
-      if sort_on is not None:
-        if sort_on.endswith(':asc'):
-          forced_sort_order = 'ascending'
-          sort_on = sort_on[:-4]
-        elif sort_on.endswith(':desc'):
-          forced_sort_order = 'descending'
-          sort_on = sort_on[:-5]
-        elif sort_on.endswith(':none'):
-          forced_sort_order = 'none'
-          sort_on = sort_on[:-5]
+      if sort_on.endswith(':asc'):
+        order = 'ascending'
+        sort_on = sort_on[:-4]
+      elif sort_on.endswith(':desc'):
+        order = 'descending'
+        sort_on = sort_on[:-5]
+      elif sort_on.endswith(':none'):
+        order = 'none'
+        sort_on = sort_on[:-5]
+      else:
+        order = None
+      # ... as well as cast type
+      i = sort_on.find(':')
+      if i < 0:
+        as_type = None
+      else:
+        as_type = sort_on[i+1:]
+        if as_type != 'float':
+          return
+        sort_on = sort_on[:i]
 
       if REQUEST is not None:
         if listbox_id is not None:
@@ -574,33 +583,23 @@ class SelectionTool( BaseTool, SimpleItem ):
 
       selection = self.getSelectionFor(selection_name, REQUEST=REQUEST)
       if selection is not None:
-        if forced_sort_order is not None:
-          if forced_sort_order == 'none':
-            temporary_new_sort_on = []
-          else:
-            temporary_new_sort_on = [(sort_on, forced_sort_order)]
+        if order is not None:
           # Allow user to sort by multiple columns
-          new_sort_on = [s
-                         for s in self.getSelectionSortOrder(selection_name)
-                         if s[0]!=sort_on]
-          new_sort_on.extend(temporary_new_sort_on)
+          new_sort_on = [s for s in selection.sort_on if s[0] != sort_on]
+          if order != 'none':
+            new_sort_on.append((sort_on, order, as_type) if as_type else
+                               (sort_on, order))
         else:
-          current_sort_on = self.getSelectionSortOrder(selection_name)
           # We must first switch from asc to desc and vice-versa if sort_order exists
           # in selection
-          n = 0
-          for current in current_sort_on:
+          order = 'ascending'
+          for current in selection.sort_on:
             if current[0] == sort_on:
-              n = 1
-              if current[1] == 'ascending':
-                new_sort_on = [(sort_on, 'descending')]
-                break
-              else:
-                new_sort_on = [(sort_on,'ascending')]
-                break
-          # And if no one exists, we just set sort
-          if n == 0:
-            new_sort_on = [(sort_on, 'ascending')]
+              if current[1] == order:
+                order = 'descending'
+              break
+          new_sort_on = ((sort_on, order, as_type) if as_type else
+                         (sort_on, order),)
         selection.edit(sort_on=new_sort_on)
 
       if REQUEST is not None:
-- 
2.30.9