[Zope-dev] Re: Zope 2.7: OrderSupport for ObjectManager

Lennart Regebro lennart@regebro.nu
Wed, 07 May 2003 11:40:48 +0200


I'm all confused now. What are the use cases for this order support again?

For me, ordered support is useful in two cases:

1. For people want to make menues or lists of document/objects, and want 
them to be in a specific order. This requires ordering of the objects, 
and to display that ordering.

2. When you make objects and services that rely on an ordered list of 
subobjects. For example, say that you have a service of some kind that 
uses several plugins to perform functions, and it has to call these 
plugins in a particular order.

Is there anything I have missed?



When it comes to UI, you need to have a way of ordering and sorting the 
objects, and of course displaying the cureent order. There are two ways 
to do that as I can see it.

1. Use the currentZMI object listing, "main.dtml". That saves you from 
having two user interfaces. It would be nice to have a sorting function 
too, to sort on id, title and date, and this is alredy availiable in the 
ZMI, and that means it doesn't have to be duplicated it.

2 Having two separate interfaces, one being the normal ZMI view, and the 
second being a ZMI tab where you order and sort the objects. There are 
benefits to this, for example it means that you can sort the ZMI object 
list without changing the ordering. It also keeps the main.dtml exactly 
as it is today, since the ordered support would instead add a separate 
tab, and it would keep the main display less cluttered.

I do not as of today have an opinion on this last matter.



When it comes to API, I would expect an API with two methods. One to 
move to a particular position, where 0 is first and -1 is last, as per 
normal Python standard, and the other where you move relatively, where 
negative numbers move you towards the front and positive towards the back.

As an example of this I give you my OrderedDictionary.py, which have:
	def order(self, key, order)
and
	def move(self, key, distance)

There would also be functions to move a set of keys one step forward, 
one step backwards, first and last that are callable through the ZMI 
with a list of ids, to make it easier to do web user interfaces.

I do have ONE opinion (wow!): I personally preferr the type of UI where 
you check the boxes of the objects you want to move, and then click a 
button to move all of them up/down/first/last as opposed to the UI that 
has one up/down/first/last button per object.

Well, that's my take on all this. I'm sure I've missed some important 
stuff, but as I said, I got all confused by the discussion. ;)


# (c) 2003 Nuxeo SARL <http://nuxeo.com>
# An Ordered, Persistent dictionary.
# Some code from David Benjamins odict
# $Id: OrderedDictionary.py,v 1.3 2003/02/21 17:14:30 regebro Exp $

from types import ListType, TupleType
from ZODB.PersistentMapping import PersistentMapping
from ZODB.PersistentList import PersistentList

class OrderedDictionary(PersistentMapping):
     def __init__(self, dict = None):
         if dict:
             self._keys = PersistentList(dict.keys())
         else:
             self._keys = PersistentList()
         PersistentMapping.__init__(self, dict)

     def __delitem__(self, key):
         PersistentMapping.__delitem__(self, key)
         self._keys.remove(key)

     def __setitem__(self, key, item):
         PersistentMapping.__setitem__(self, key, item)
         if key not in self._keys: self._keys.append(key)

     def __cmp__(self, dict):
         if isinstance(dict, OrderedDictionary):
             return cmp(self.data, dict.data) and cmp(self._keys, 
dict._keys)
         else:
             return 0

     def clear(self):
         PersistentMapping.clear(self)
         self._keys = PersistentList()

     def copy(self):
         mcopy = OrderedDictionary()
         mcopy.data = self.data.copy()
         mcopy._keys = self._keys[:]
         return mcopy

     def items(self):
         return zip(self._keys, self.values())

     def keys(self):
         return self._keys[:] # This returns a non-persistent copy of 
self._keys,
                              # so you can manipulate the copy without 
manipulating self._keys

     def popitem(self):
         try:
             key = self._keys[-1]
         except IndexError:
             raise KeyError('dictionary is empty')

         val = self[key]
         del self[key]

         return (key, val)

     def index(self, key):
         return self._keys.index(key)

     def setdefault(self, key, failobj = None):
         if key not in self._keys: self._keys.append(key)
         return PersistentMapping.setdefault(self, key, failobj)

     def update(self, dict):
         for (key,val) in dict.items():
             self.__setitem__(key,val)

     def values(self):
         return map(self.get, self._keys)

     def order(self, key, order ):
         """Moves the specified item to the specifed position

         0 is first, 1 is second, -1 is last, -1 second from last, and 
so on.
         """
         if order < 0:
             order = len(self) + order # Negative numbers are counted 
from behind
         if order < 0:
             order = 0 # It's still negative: move first
         self._keys.remove(key)
         if order >= len(self._keys):
             self._keys.append(key)
         else:
             self._keys.insert(order, key)

     def move(self, key, distance):
         """Moves the specified item a number of positions

         Positive numbers move to higher index numbers, negavtive to 
lower index numbers"""
         oldpos = self.index(key)
         newpos = oldpos + distance
         if newpos < 0:
             newpos = 0
         self.order(key, newpos)