[<<] Industrie Toulouse
We have movable Parts!. That's right. With a little bit of code, a couple of unit tests, and some surprisingly simple UI work, Parts in an SCS Document can be re-arranged.

This is yielding something I've been wanting to do (and have) for years - decent through-the-web Document editing, with drop in objects than can be reorganized easily. While I'm sure it's been done many times before, it was great seeing the results yesterday as part of my own framework and application.

The work went fairly quickly, once I moved the methods to the right class and added Unit Tests. The secret is in the _objects attribute of Zope ObjectManager based instances. _objects is a tuple of dictionaries with the keys 'id' and 'meta_type'. ObjectManager classes use this information to know what objects in the ObjectManager are being managed as subobjects, and to define their ordering. Most people never see or know of this attributes existence as its use is well-handled by the common ObjectManager API's. But one can use this list to their advantage, such as reordering.

So, in todays code we have what I used to reorder Parts in my PartContainer class. There are two important elements - finding the index of the object in question, and then swapping its place in the list. We can't use parts.index(part_id) because of the complex data structure of the list - although, if we had the object in question, we could try parts.index({'id': obj.getId(), 'meta_type': obj.meta_type}). But the following works, and could be applied to any ObjectManager based class. Note - there are already Products available to bring order to Folders in Zope, such as Ordered Folder. It's a feature that was Proposed for Zope 2.6, but was deferred..

class PartContainer(ObjectManager):
    def _findPartIndex(self, part_id, objectList=[]):
        """ Returns the index of a part in the object list """
        idx = 0
        for obj in objectList:
            if obj['id'] == part_id:
                break
            idx += 1
        else:
            ## part not found
            raise IndexError(part_id)
        return idx
 
    security.declareProtected(change_scs_documents, 'movePartUp')
    def movePartUp(self, part_id):
        """ Move a part up.  Raises IndexError if part is not found """
        ## make a copy of our _objects list, which is a part of ObjectManager.
        ## _objects is a tuple of dictionaries with keys 'meta_type' and 'id'.
        parts = list(self._objects)
        idx = self._findPartIndex(part_id, parts)
        if idx == 0:
            ## Can't move past top, just exit
            return 0
        ## swap fields, moving idx up
        parts[idx], parts[idx-1] = parts[idx-1], parts[idx]
        self._objects = tuple(parts)
        return 1
 
    security.declareProtected(change_scs_documents, 'movePartDown')
    def movePartDown(self, part_id):
        """ Move a part down.  Raises IndexError if part is not found """
        ## make a copy of our _objects list, which is a part of ObjectManager.
        ## _objects is a tuple of dictionaries with keys 'meta_type' and 'id'.
        parts = list(self._objects)
        idx = self._findPartIndex(part_id, parts)
        if idx == len(parts)-1:
            ## Can't move past bottom, so just exit
            return 0
        ## swap fields, moving idx up
        parts[idx], parts[idx+1] = parts[idx+1], parts[idx]
        self._objects = tuple(parts)
        return 1