Commit 158eaf85 authored by Jérome Perrin's avatar Jérome Perrin

wip py3 sort categories

parent 97eb88eb
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
from past.builtins import cmp from past.builtins import cmp
import string import string
import warnings
from Products.ERP5Type.Globals import InitializeClass, DTMLFile from Products.ERP5Type.Globals import InitializeClass, DTMLFile
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
...@@ -282,6 +283,7 @@ class Category(Folder): ...@@ -282,6 +283,7 @@ class Category(Folder):
def getCategoryChildValueList(self, recursive=1, include_if_child=1, def getCategoryChildValueList(self, recursive=1, include_if_child=1,
is_self_excluded=1, sort_on=None, is_self_excluded=1, sort_on=None,
sort_order=None, local_sort_method=None, sort_order=None, local_sort_method=None,
local_sort_key=None,
local_sort_id=None, checked_permission=None, local_sort_id=None, checked_permission=None,
**kw): **kw):
""" """
...@@ -305,8 +307,13 @@ class Category(Folder): ...@@ -305,8 +307,13 @@ class Category(Folder):
WARNING: using these parameters can slow down WARNING: using these parameters can slow down
significantly, because this is written in Python significantly, because this is written in Python
local_sort_key - When using the default preorder traversal, use
this function as sort key for objects of the same
depth.
local_sort_method - When using the default preorder traversal, use local_sort_method - When using the default preorder traversal, use
this function to sort objects of the same depth. this function to sort objects of the same depth.
DEPRECATED, use local_sort_key
local_sort_id - When using the default preorder traversal, sort local_sort_id - When using the default preorder traversal, sort
objects of the same depth by comparing their objects of the same depth by comparing their
...@@ -325,24 +332,23 @@ class Category(Folder): ...@@ -325,24 +332,23 @@ class Category(Folder):
child_value_list = self.objectValues(self.allowed_types) child_value_list = self.objectValues(self.allowed_types)
if local_sort_id: if local_sort_id:
if isinstance(local_sort_id, (tuple, list)): if not isinstance(local_sort_id, (tuple, list)):
def sort_method(a, b): local_sort_id = (local_sort_id, )
for sort_id in local_sort_id: def sort_key(c):
diff = cmp(a.getProperty(sort_id, 0), b.getProperty(sort_id, 0)) return [c.getProperty(sort_id, 0) for sort_id in local_sort_id]
if diff != 0: local_sort_key = sort_key
return diff
return 0
local_sort_method = sort_method
else:
local_sort_method = lambda a, b: cmp(a.getProperty(local_sort_id, 0),
b.getProperty(local_sort_id, 0))
if local_sort_method: if local_sort_method:
# sort objects at the current level warnings.warn(
"`local_sort_method` argument is deprecated, use `local_sort_key` instead",
DeprecationWarning)
child_value_list = list(child_value_list) child_value_list = list(child_value_list)
if six.PY2: if six.PY2:
child_value_list.sort(local_sort_method) child_value_list.sort(local_sort_method)
else: else:
child_value_list.sort(key=cmp_to_key(local_sort_method)) local_sort_key = cmp_to_key(local_sort_method)
if local_sort_key:
child_value_list = sorted(child_value_list, key=local_sort_key)
if recursive: if recursive:
for c in child_value_list: for c in child_value_list:
# Do not pass sort_on / sort_order parameters intentionally, because # Do not pass sort_on / sort_order parameters intentionally, because
...@@ -351,6 +357,7 @@ class Category(Folder): ...@@ -351,6 +357,7 @@ class Category(Folder):
is_self_excluded=0, is_self_excluded=0,
include_if_child=include_if_child, include_if_child=include_if_child,
local_sort_method=local_sort_method, local_sort_method=local_sort_method,
local_sort_key=local_sort_key,
local_sort_id=local_sort_id)) local_sort_id=local_sort_id))
else: else:
for c in child_value_list: for c in child_value_list:
...@@ -838,7 +845,7 @@ class BaseCategory(Category): ...@@ -838,7 +845,7 @@ class BaseCategory(Category):
'getCategoryChildValueList') 'getCategoryChildValueList')
def getCategoryChildValueList(self, is_self_excluded=1, recursive=1, def getCategoryChildValueList(self, is_self_excluded=1, recursive=1,
include_if_child=1, sort_on=None, sort_order=None, include_if_child=1, sort_on=None, sort_order=None,
local_sort_method=None, local_sort_id=None, local_sort_method=None, local_sort_key=None, local_sort_id=None,
checked_permission=None, **kw): checked_permission=None, **kw):
""" """
List the child objects of this category and all its subcategories. List the child objects of this category and all its subcategories.
...@@ -860,8 +867,13 @@ class BaseCategory(Category): ...@@ -860,8 +867,13 @@ class BaseCategory(Category):
the 'sort_on' attribute. The default is to do a preorder tree the 'sort_on' attribute. The default is to do a preorder tree
traversal on all subobjects. traversal on all subobjects.
local_sort_key - When using the default preorder traversal, use
this function as sort key for objects of the same
depth.
local_sort_method - When using the default preorder traversal, use local_sort_method - When using the default preorder traversal, use
this function to sort objects of the same depth. this function to sort objects of the same depth.
DEPRECATED, use local_sort_key
local_sort_id - When using the default preorder traversal, sort local_sort_id - When using the default preorder traversal, sort
objects of the same depth by comparing their objects of the same depth by comparing their
...@@ -879,32 +891,31 @@ class BaseCategory(Category): ...@@ -879,32 +891,31 @@ class BaseCategory(Category):
child_value_list = self.objectValues(self.allowed_types) child_value_list = self.objectValues(self.allowed_types)
if local_sort_id: if local_sort_id:
if isinstance(local_sort_id, (tuple, list)): if not isinstance(local_sort_id, (tuple, list)):
def sort_method(a, b): local_sort_id = (local_sort_id, )
for sort_id in local_sort_id: def sort_key(c):
diff = cmp(a.getProperty(sort_id, 0), b.getProperty(sort_id, 0)) return [c.getProperty(sort_id, 0) for sort_id in local_sort_id]
if diff != 0: local_sort_key = sort_key
return diff
return 0
local_sort_method = sort_method
else:
local_sort_method = lambda a, b: cmp(a.getProperty(local_sort_id, 0),
b.getProperty(local_sort_id, 0))
if local_sort_method: if local_sort_method:
# sort objects at the current level warnings.warn(
"`local_sort_method` argument is deprecated, use `local_sort_key` instead",
DeprecationWarning)
child_value_list = list(child_value_list) child_value_list = list(child_value_list)
if six.PY2: if six.PY2:
child_value_list.sort(local_sort_method) child_value_list.sort(local_sort_method)
else: else:
child_value_list.sort(key=cmp_to_key(local_sort_method)) local_sort_key = cmp_to_key(local_sort_method)
if local_sort_key:
child_value_list = sorted(child_value_list, key=local_sort_key)
if recursive: if recursive:
for c in child_value_list: for c in child_value_list:
value_list.extend(c.getCategoryChildValueList(recursive=1, value_list.extend(c.getCategoryChildValueList(recursive=1,
is_self_excluded=0, is_self_excluded=0,
include_if_child=include_if_child, include_if_child=include_if_child,
local_sort_id=local_sort_id, local_sort_method=local_sort_method,
local_sort_method=local_sort_method)) local_sort_key=local_sort_key,
local_sort_id=local_sort_id))
else: else:
for c in child_value_list: for c in child_value_list:
if include_if_child: if include_if_child:
......
...@@ -30,6 +30,9 @@ ...@@ -30,6 +30,9 @@
from Products.CMFCategory.Filter import Filter from Products.CMFCategory.Filter import Filter
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from zLOG import LOG, PROBLEM from zLOG import LOG, PROBLEM
import six
if six.PY3:
from functools import cmp_to_key
class Renderer(Filter): class Renderer(Filter):
""" """
...@@ -42,7 +45,7 @@ class Renderer(Filter): ...@@ -42,7 +45,7 @@ class Renderer(Filter):
def __init__(self, spec = None, filter = None, portal_type = None, def __init__(self, spec = None, filter = None, portal_type = None,
display_id = None, sort_id = None, display_id = None, sort_id = None,
display_method = None, sort_method = None, filter_method = None, display_method = None, sort_key = None, sort_method = None, filter_method = None,
filter_node=0, disable_node=0, filter_node=0, disable_node=0,
filter_leave=0, disable_leave=0, filter_leave=0, disable_leave=0,
is_right_display = 0, translate_display = 0, is_right_display = 0, translate_display = 0,
...@@ -74,7 +77,10 @@ class Renderer(Filter): ...@@ -74,7 +77,10 @@ class Renderer(Filter):
foo2 5 foo2 5
display order will be (foo1, foo, foo2) display order will be (foo1, foo, foo2)
- *sort_method*: a callable method which provides a sort function (?la cmp) - *sort_key*: a callable method used to sort the values, as in sort(key=sort_key)
- *sort_method*: a callable method used to sort the values, as in python2 cmp.
DEPRECATED, use sort_key.
- *is_right_display*: use the right value in the couple as the display value. - *is_right_display*: use the right value in the couple as the display value.
...@@ -109,6 +115,7 @@ class Renderer(Filter): ...@@ -109,6 +115,7 @@ class Renderer(Filter):
self.display_id = display_id self.display_id = display_id
self.sort_id = sort_id self.sort_id = sort_id
self.display_method = display_method self.display_method = display_method
self.sort_key = sort_key
self.sort_method = sort_method self.sort_method = sort_method
self.is_right_display = is_right_display self.is_right_display = is_right_display
self.translate_display = translate_display self.translate_display = translate_display
...@@ -135,7 +142,12 @@ class Renderer(Filter): ...@@ -135,7 +142,12 @@ class Renderer(Filter):
value_list = self.getObjectList(value_list) value_list = self.getObjectList(value_list)
value_list = self.filter(value_list) value_list = self.filter(value_list)
if self.sort_method is not None: if self.sort_method is not None:
value_list.sort(self.sort_method) if six.PY2:
value_list.sort(self.sort_method)
else:
value_list.sort(key=cmp_to_key(self.sort_method))
elif self.sort_key is not None:
value_list.sort(key=self.sort_key)
elif self.sort_id is not None: elif self.sort_id is not None:
value_list.sort(key=lambda x: x.getProperty(self.sort_id)) value_list.sort(key=lambda x: x.getProperty(self.sort_id))
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
import mock import mock
from collections import deque from collections import deque
import unittest import unittest
import warnings
from Acquisition import aq_base from Acquisition import aq_base
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
...@@ -952,8 +953,8 @@ class TestCMFCategory(ERP5TypeTestCase): ...@@ -952,8 +953,8 @@ class TestCMFCategory(ERP5TypeTestCase):
self.assertSameSet(c1.getCategoryChildValueList(is_self_excluded=0), self.assertSameSet(c1.getCategoryChildValueList(is_self_excluded=0),
(c1, c11, c111)) (c1, c11, c111))
def test_24_getCategoryChildValueListLocalSortMethod(self): def test_24_getCategoryChildValueListLocalSort(self):
'''Test getCategoryChildValueList local sort method''' '''Test getCategoryChildValueList local sort'''
pc = self.getCategoriesTool() pc = self.getCategoriesTool()
bc = pc.newContent(portal_type='Base Category', id='child_test') bc = pc.newContent(portal_type='Base Category', id='child_test')
c1 = bc.newContent(portal_type='Category', id='1', int_index=10, title='C') c1 = bc.newContent(portal_type='Category', id='1', int_index=10, title='C')
...@@ -974,17 +975,52 @@ class TestCMFCategory(ERP5TypeTestCase): ...@@ -974,17 +975,52 @@ class TestCMFCategory(ERP5TypeTestCase):
# sort_order which sort the whole list regardless of the original # sort_order which sort the whole list regardless of the original
# structure). # structure).
# This can be done either with a function (like cmp argument to python sort_key_calls = []
# list sort) def sort_key(c):
sort_key_calls.append(c)
return c.getTitle()
# here c1, c2, c3 are sorted by their titles
self.assertEqual(list(bc.getCategoryChildValueList(
local_sort_key=sort_key)),
[c3, c2, c1, c11, c111, c12])
self.assertTrue(sort_key_calls)
# here c11 & c12 are sorted by their titles
self.assertEqual(list(c1.getCategoryChildValueList(
local_sort_key=sort_key)), [c11, c111, c12])
self.assertTrue(sort_key_calls)
# This can be done with a function, using `local_sort_key` or
# `local_sort_method` (with is like cmp argument to python2 list sort)
sort_func_calls = []
def sort_func(a, b): def sort_func(a, b):
sort_func_calls.append((a, b))
return cmp(a.getTitle(), b.getTitle()) return cmp(a.getTitle(), b.getTitle())
# `local_sort_method` is deprecated, so using it cause a warning to be
# emitted. Because the method exists on both category and base category
# there can be two warnings.
with warnings.catch_warnings(record=True) as warning_list:
c1.getCategoryChildValueList(local_sort_method=sort_func)
self.assertEqual(
[str(w.message) for w in warning_list],
['`local_sort_method` argument is deprecated, use `local_sort_key` instead'])
with warnings.catch_warnings(record=True) as warning_list:
bc.getCategoryChildValueList(local_sort_method=sort_func)
self.assertEqual(
[str(w.message) for w in warning_list],
['`local_sort_method` argument is deprecated, use `local_sort_key` instead'] * 2)
sort_func_calls.clear()
# here c1, c2, c3 are sorted by their titles # here c1, c2, c3 are sorted by their titles
self.assertEqual(list(bc.getCategoryChildValueList( self.assertEqual(list(bc.getCategoryChildValueList(
local_sort_method=sort_func)), local_sort_method=sort_func)),
[c3, c2, c1, c11, c111, c12]) [c3, c2, c1, c11, c111, c12])
self.assertTrue(sort_func_calls)
sort_func_calls.clear()
# here c11 & c12 are sorted by their titles # here c11 & c12 are sorted by their titles
self.assertEqual(list(c1.getCategoryChildValueList( self.assertEqual(list(c1.getCategoryChildValueList(
local_sort_method=sort_func)), [c11, c111, c12]) local_sort_method=sort_func)), [c11, c111, c12])
self.assertTrue(sort_func_calls)
# This can also be done with a local_sort_id, then objects are sorted by # This can also be done with a local_sort_id, then objects are sorted by
# comparing this 'sort_id' property (using getProperty()) # comparing this 'sort_id' property (using getProperty())
......
...@@ -33,7 +33,7 @@ from six import string_types as basestring ...@@ -33,7 +33,7 @@ from six import string_types as basestring
from six.moves import xrange from six.moves import xrange
import six import six
if six.PY3: if six.PY3:
from functools import cmp_to_key from functools import cmp_to_key, total_ordering
import os import os
import re import re
import string import string
...@@ -146,13 +146,21 @@ else: ...@@ -146,13 +146,21 @@ else:
if six.PY2: if six.PY2:
OrderableKey = lambda x: x OrderableKey = lambda x: x
else: else:
@total_ordering
class OrderableKey(object): class OrderableKey(object):
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
def __lt__(self, other): def __lt__(self, other):
if not isinstance(other, OrderableKey):
raise TypeError
return cmp(self.value, other.value) != 1 return cmp(self.value, other.value) != 1
def __eq__(self, other):
if not isinstance(other, OrderableKey):
raise TypeError
return self.value == other.value
def __repr__(self): def __repr__(self):
return 'OrderableKey(%r)' % self.value return 'OrderableKey(%r)' % self.value
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment