diff --git a/product/ERP5Type/XMLMatrix.py b/product/ERP5Type/XMLMatrix.py index 102eb8ec64fe87146c1e4572223bdef9e4fb6bb7..c694e31000cf23e0a35e4db7a54f7fa5730981af 100644 --- a/product/ERP5Type/XMLMatrix.py +++ b/product/ERP5Type/XMLMatrix.py @@ -37,6 +37,8 @@ from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter from zLOG import LOG +INFINITE_SET = type('', (object,), {'__contains__': lambda *args: True})() + class XMLMatrix(Folder): """ A mix-in class which provides a matrix like @@ -143,142 +145,64 @@ class XMLMatrix(Folder): security.declareProtected( Permissions.ModifyPortalContent, '_setCellRange' ) - def _setCellRange(self, *kw, **kwd): - """ - Set a new range for a matrix. - If needed, it will resize and/or reorder matrix content. - """ - movement = {} # Maps original cell id to its new id for each moved cell. - new_index = PersistentMapping() - - base_id = kwd.get('base_id', 'cell') - if getattr(aq_base(self), 'index', None) is None: - self.index = PersistentMapping() - - # Return if previous range is the same - current_range = self.getCellRange(base_id=base_id) - if current_range == list(kw): # kw is a tuple - return - - # Create the new index for the range given in *kw - # *kw practical example: - # kw = [ ['color/blue', 'color/red'], ['size/S', 'size/L']] - for i, index_ids in enumerate(kw): - temp = PersistentMapping() - for j, my_id in enumerate(index_ids): - temp[my_id] = j - new_index[i] = temp - - if self.index.has_key(base_id): - # Compute cell movement from their position in previous range to their - # position in the new range. - for i, i_value in self.index[base_id].iteritems(): - if new_index.has_key(i): - temp = {} - for my_id, my_value in i_value.iteritems(): - temp[my_value] = new_index[i].get(my_id) - movement[i] = temp - - # List all valid cell ids for current base_id. - object_id_list = [] - for obj in self.objectValues(): - object_id = obj.getId() - if object_id.find(base_id) == 0: - # Check that all '_'-separated fields are of int type. - if (object_id) > len(base_id): - try: - int(object_id[len(base_id)+1:].split('_')[0]) - test = self._getOb(object_id) # If the object was created - # during this transaction, - # then we do not need to - # work on it - object_id_list.append(object_id) - except (ValueError, KeyError): - pass - - # Prepend 'temp_' to all cells, to avoid id conflicts while renaming. - for object_id in object_id_list: - new_name = 'temp_' + object_id - obj = self._getOb(object_id) - self._delObject(object_id) - obj.id = new_name - self._setObject(new_name, aq_base(obj)) - - # Rename all cells to their final name. - for object_id in object_id_list: - # Retrieve the place of the object, for movement_0_0 it is ['0','0'] - object_place = object_id[len(base_id)+1:].split('_') - to_delete = 1 - # We must have the same number of dimensions - if len(object_place) == len(new_index): - # Let us browse each dimension of the previous index - for i in range(len(object_place)): - # Build each part of the nex id by looking up int. values - old_place = int(object_place[i]) - # We are looking inside the movement dictionnary where - # we should move the object, so for example - # 'qantity_2_5' is renamed as 'quantity_4_3' - if movement.has_key(i): - if movement[i].has_key(old_place): - # Replace the place of the cell only if there where a change - if (movement[i][old_place]) != None: - object_place[i] = str(movement[i][old_place]) - to_delete = 0 - else: - object_place[i] = None - - # XXX In this case, we delete every cell wich are not in the - # movement dictionnary, may be this is very bad, so may be - # we need to add an extra check here, ie if - # if movement[i].has_key(old_place) returns None, - # We may want to keep the cell, but only if we are sure - # the movement dictionnary will not define a new cell with this id - - new_object_id_list = [] - - temp_object_id = 'temp_' + object_id - o = self._getOb(temp_object_id) - if not to_delete and not (None in object_place): - self._delObject(temp_object_id) # In all cases, we have - # to remove the temp object - object_place.insert(0, base_id) - new_name = '_'.join(object_place) - o.id = new_name - new_object_id_list.extend(new_name) - self._setObject(new_name, aq_base(o)) - - if new_name != object_id: - # Theses two lines are very important, if the object is renamed - # then we must uncatalog the previous one - o.unindexObject(path='%s/%s' % (self.getUrl() , object_id)) - # Force a new uid to be allocated, because unindexObject creates - # an activity which will later delete lines from catalog based - # on their uid, and it is not garanted that indexation will happen - # after this deletion. - # It is bad to waste uids, but this data structure offers no - # alternative because cell id gives its index in the matrix, - # so reordering axes requires the cell id to change. - # XXX: It can be improved, but requires most of this file to be - # rewritten, and compatibility code must be written as data - # structure would most probably change. - o.uid = None - o.reindexObject() # we reindex in case position has changed - # uid should be consistent + def _setCellRange(self, *args, **kw): + """Set a new range for a matrix + + Each value for each axis is assigned an integer id. + If the number of axis changes, everything is reset. + Otherwise, ids are never changed, so that cells never need to be renamed: + this means no sort is garanteed, and there can be holes. + """ + base_id = kw.get('base_id', 'cell') + # Get (initialize if necessary) index for considered matrix (base_id). + try: + index = aq_base(self).index + except AttributeError: + index = self.index = PersistentMapping() + to_delete = [] + try: + index = index[base_id] + if len(args) != len(index): + # The number of axis changes so we'll delete all existing cells and + # renumber everything from 1. + to_delete = INFINITE_SET, + index.clear() + except KeyError: + index[base_id] = index = PersistentMapping() + # For each axis ... + for i, axis in enumerate(args): + # ... collect old axis keys and allocate ids for new ones. + axis = set(axis) + last_id = -1 + try: + id_dict = index[i] + except KeyError: + index[i] = id_dict = PersistentMapping() else: - # In all cases, we have to remove the temp object - #WARNING -> if path is not good, it will not be able to uncatalog !!! - #o.immediateReindexObject() # STILL A PROBLEM -> getUidForPath XXX - - if object_id not in new_object_id_list: # do not unindex a new object - o.isIndexable = ConstantGetter('isIndexable', value=True) - o.unindexObject(path='%s/%s' % (self.getUrl() , object_id)) - # unindexed already forced - o.isIndexable = ConstantGetter('isIndexable', value=False) - self._delObject(temp_object_id) # object will be removed - # from catalog automaticaly - # We don't need the old index any more, we - # can set the new index - self.index[base_id] = new_index + delete = set() + to_delete.append(delete) + for k, v in id_dict.items(): + try: + axis.remove(k) + if last_id < v: + last_id = v + except KeyError: + delete.add(v) + del id_dict[k] + # At this point, last_id contains the greatest id. + for k in sorted(axis): + last_id += 1 + id_dict[k] = last_id + # Remove old cells if any. + if any(to_delete): + prefix = base_id + '_' + prefix_len = len(prefix) + for cell_id in list(self.objectIds()): + if cell_id.startswith(prefix): + for i, j in enumerate(cell_id[prefix_len:].split('_')): + if int(j) in to_delete[i]: + self._delObject(cell_id) + break security.declareProtected( Permissions.ModifyPortalContent, 'setCellRange' ) def setCellRange(self, *kw, **kwd): @@ -427,17 +351,11 @@ class XMLMatrix(Folder): """ Returns the cell range as a list of index ids """ - if getattr(aq_base(self), 'index', None) is None: + try: + cell_range = aq_base(self).index[base_id] + except (AttributeError, KeyError): return [] - cell_range = self.index.get(base_id, None) - if cell_range is None: - return None - - result = [] - for value in cell_range.itervalues(): - result_items = sorted(value.iteritems(), key=lambda x:x[1]) - result.append([x[0] for x in result_items]) - return result + return [x.keys() for _, x in sorted(cell_range.iteritems())] security.declareProtected( Permissions.ModifyPortalContent, 'newCell' ) def newCell(self, *kw, **kwd): diff --git a/product/ERP5Type/tests/testXMLMatrix.py b/product/ERP5Type/tests/testXMLMatrix.py index e510f50fea3c8ad6d27cfa3d7f98ee4ab088e195..633338c0358f129d5f35b386b42bc5ce79175771 100644 --- a/product/ERP5Type/tests/testXMLMatrix.py +++ b/product/ERP5Type/tests/testXMLMatrix.py @@ -89,7 +89,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['1', '2', '3'], ['a', 'b', 'c']] kwd = {'base_id' : 'quantity'} matrix.renameCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) i = 0 for place in cartesianProduct(cell_range): @@ -100,7 +100,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['2', '3', '4'], ['b', 'c', 'd']] matrix.renameCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) i = 0 for place in cartesianProduct(cell_range): cell = matrix.getCell(*place, **kwd) @@ -111,7 +111,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['1', '2', '3', '4'], ['a', 'b', 'c', 'd']] value_list = (0, 1, 2, None, 3, 4, 5, None, 6, 7, 8, None, None, None, None, None) matrix.renameCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) i = 0 for place in cartesianProduct(cell_range): cell = matrix.getCell(*place, **kwd) @@ -125,7 +125,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['1', '2'], ['a', 'b']] value_list = (0, 1, 3, 4) matrix.renameCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) i = 0 for place in cartesianProduct(cell_range): cell = matrix.getCell(*place, **kwd) @@ -136,7 +136,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['3'], ['a', 'b', 'c'], ['A', 'B', 'C']] value_list = (0, None, None, 1, None, None, None, None, None) matrix.renameCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) i = 0 for place in cartesianProduct(cell_range): cell = matrix.getCell(*place, **kwd) @@ -150,7 +150,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['1', '2'], ['A', 'B']] value_list = (0, 1, None, None) matrix.renameCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) i = 0 for place in cartesianProduct(cell_range): cell = matrix.getCell(*place, **kwd) @@ -171,7 +171,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['1', '2'], ['A', 'B'], ['a', 'b']] value_list = (0, None, 1, None, 2, None, 3, None) matrix.renameCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) i = 0 for place in cartesianProduct(cell_range): cell = matrix.getCell(*place, **kwd) @@ -200,7 +200,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['1', '2', '3'], ['a', 'b', 'c']] kwd = {'base_id' : 'quantity'} matrix.setCellRange(*cell_range, **kwd) - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) for place in cartesianProduct(cell_range): cell = matrix.newCell(portal_type="Purchase Order Cell", @@ -215,7 +215,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): matrix.setCellRange(*cell_range, **kwd) # We must commit transaction in order to put cell reindexing in activity queue transaction.commit() - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) next_cell_id_list = list(matrix.objectIds()) # the cells on coordinates 2b, 3b, 3b and 3c are kept self.assertEquals(4, len(next_cell_id_list)) @@ -235,7 +235,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['0', '1'], ['a','b']] matrix.setCellRange(*cell_range, **kwd) transaction.commit() - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) next2_cell_id_list = list(matrix.objectIds()) removed_id_list = filter(lambda x: x not in next2_cell_id_list,next_cell_id_list) self.tic() @@ -248,7 +248,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): kwd = {'base_id' : 'movement'} matrix.setCellRange(*cell_range, **kwd) transaction.commit() - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) self.tic() for id in next2_cell_id_list: self.assertFalse(catalog.hasPath(url + '/' + id)) @@ -263,7 +263,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): # if we keep the same range, nothing happens matrix.setCellRange(*cell_range, **kwd) transaction.commit() - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) self.assertEqual(len(matrix.getCellValueList(**kwd)), 2) self.tic() @@ -275,7 +275,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['0', '2'], ['a', ], ['Z']] matrix.setCellRange(*cell_range, **kwd) transaction.commit() - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) self.tic() # in this case, cells has been removed @@ -295,7 +295,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): cell_range = [['1', '2'], ['a', ], ['X']] matrix.setCellRange(*cell_range, **kwd) transaction.commit() - self.assertEqual(matrix.getCellRange(**kwd), cell_range) + self.assertEqual(map(set, matrix.getCellRange(**kwd)), map(set, cell_range)) self.tic() # in this case, cells has been removed @@ -438,8 +438,7 @@ class TestXMLMatrix(ERP5TypeTestCase, LogInterceptor): self.assertFalse('quantity_0_1' in matrix.objectIds()) cell = matrix.getCell('2', 'b', **kwd) - # this is the same cell, but it just changed id - self.assertEquals('quantity_0_0', cell.getId()) + self.assertEquals('quantity_1_1', cell.getId()) self.assertEquals('This one', cell.getTitle()) transaction.commit()