[Checkins] SVN: grok/trunk/ Added an ordered container component.

Jan-Wijbrand Kolman janwijbrand at gmail.com
Thu May 1 10:03:06 EDT 2008


Log message for revision 86001:
  Added an ordered container component.

Changed:
  U   grok/trunk/CHANGES.txt
  U   grok/trunk/src/grok/__init__.py
  U   grok/trunk/src/grok/components.py
  U   grok/trunk/src/grok/interfaces.py
  A   grok/trunk/src/grok/tests/container/orderedcontainer.py
  A   grok/trunk/src/grok/tests/container/orderedcontainerfiresevent.py

-=-
Modified: grok/trunk/CHANGES.txt
===================================================================
--- grok/trunk/CHANGES.txt	2008-05-01 13:45:49 UTC (rev 86000)
+++ grok/trunk/CHANGES.txt	2008-05-01 14:03:05 UTC (rev 86001)
@@ -7,10 +7,12 @@
 Feature changes
 ---------------
 
+* Added an OrderedContainer component.
+
 * Merged the versions from the 3.4 KGS:
   http://download.zope.org/zope3.4/versions-3.4.0c1.cfg
 
-  We are now using the latest Zope 3 releases for all the Zope 
+  We are now using the latest Zope 3 releases for all the Zope
   For upgrade notes, see doc/upgrade.txt for more information.
 
 * Added support for easier testsetup based on z3c.testsetup. This is a

Modified: grok/trunk/src/grok/__init__.py
===================================================================
--- grok/trunk/src/grok/__init__.py	2008-05-01 13:45:49 UTC (rev 86000)
+++ grok/trunk/src/grok/__init__.py	2008-05-01 14:03:05 UTC (rev 86001)
@@ -33,7 +33,8 @@
 from grokcore.component import Adapter, MultiAdapter, GlobalUtility
 from grok.components import Model, View
 from grok.components import XMLRPC, REST, JSON
-from grok.components import PageTemplate, PageTemplateFile, Container, Traverser
+from grok.components import PageTemplate, PageTemplateFile, Traverser
+from grok.components import Container, OrderedContainer
 from grok.components import Site, LocalUtility, Annotation
 from grok.components import Application, Form, AddForm, EditForm, DisplayForm
 from grok.components import Indexes

Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py	2008-05-01 13:45:49 UTC (rev 86000)
+++ grok/trunk/src/grok/components.py	2008-05-01 14:03:05 UTC (rev 86001)
@@ -43,6 +43,9 @@
 from zope.app.container.btree import BTreeContainer
 from zope.app.container.contained import Contained
 from zope.app.container.interfaces import IReadContainer, IObjectAddedEvent
+from zope.app.container.interfaces import IOrderedContainer
+from zope.app.container.contained import notifyContainerModified
+from persistent.list import PersistentList
 from zope.app.component.site import SiteManagerContainer
 from zope.app.component.site import LocalSiteManager
 
@@ -67,6 +70,47 @@
     interface.implements(IAttributeAnnotatable)
 
 
+class OrderedContainer(Container):
+    interface.implements(IOrderedContainer)
+
+    def __init__(self):
+        super(OrderedContainer, self).__init__()
+        self._order = PersistentList()
+
+    def keys(self):
+        # Return a copy of the list to prevent accidental modifications.
+        return self._order[:]
+
+    def __iter__(self):
+        return iter(self.keys())
+
+    def values(self):
+        return (self[key] for key in self._order)
+
+    def items(self):
+        return ((key, self[key]) for key in self._order)
+
+    def __setitem__(self, key, object):
+        foo = self.has_key(key)
+        # Then do whatever containers normally do.
+        super(OrderedContainer, self).__setitem__(key, object)
+        if not foo:
+            self._order.append(key)
+
+    def __delitem__(self, key):
+        # First do whatever containers normally do.
+        super(OrderedContainer, self).__delitem__(key)
+        self._order.remove(key)
+
+    def updateOrder(self, order):
+        if set(order) != set(self._order):
+            raise ValueError("Incompatible key set.")
+
+        self._order = PersistentList()
+        self._order.extend(order)
+        notifyContainerModified(self)
+
+
 class Site(SiteManagerContainer):
     pass
 
@@ -616,7 +660,7 @@
         """See zope.contentprovider.interfaces.IContentProvider"""
         # Now render the view
         if self.template:
-            return self.template.render(self) 
+            return self.template.render(self)
         else:
             viewlets = grokcore.component.util.sort_components(self.viewlets)
             return u'\n'.join([viewlet.render() for viewlet in viewlets])

Modified: grok/trunk/src/grok/interfaces.py
===================================================================
--- grok/trunk/src/grok/interfaces.py	2008-05-01 13:45:49 UTC (rev 86000)
+++ grok/trunk/src/grok/interfaces.py	2008-05-01 14:03:05 UTC (rev 86001)
@@ -28,6 +28,7 @@
     Model = interface.Attribute("Base class for persistent content objects "
                                 "(models).")
     Container = interface.Attribute("Base class for containers.")
+    OrderedContainer = interface.Attribute("Base class for ordered containers.")
     Site = interface.Attribute("Mixin class for sites.")
     Application = interface.Attribute("Base class for applications.")
     Adapter = interface.Attribute("Base class for adapters.")
@@ -162,7 +163,7 @@
     def order(value=None):
         """Control the ordering of components.
 
-        If the value is specified, the order will be determined by sorting on 
+        If the value is specified, the order will be determined by sorting on
         it.
         If no value is specified, the order will be determined by definition
         order within the module.
@@ -173,7 +174,7 @@
         Inter-module order is by dotted name of the module the
         components are in; unless an explicit argument is specified to
         ``grok.order()``, components are grouped by module.
-  
+
         The function grok.util.sort_components can be used to sort
         components according to these rules.
         """
@@ -441,7 +442,7 @@
 
     request = interface.Attribute("Request that REST handler was looked"
                                   "up with.")
-    
+
     body = interface.Attribute(
         """The text of the request body.""")
 
@@ -468,22 +469,22 @@
     """
 
 class ITemplateFileFactory(interface.Interface):
-    """Utility that generates templates from files in template directories. 
+    """Utility that generates templates from files in template directories.
     """
-    
+
     def __call__(filename, _prefix=None):
         """Creates an ITemplate from a file
-        
+
         _prefix is the directory the file is located in
         """
 
 class ITemplate(interface.Interface):
     """Template objects
     """
-    
+
     def _initFactory(factory):
         """Template language specific initializations on the view factory."""
-        
+
     def render(view):
         """Renders the template"""
 

Added: grok/trunk/src/grok/tests/container/orderedcontainer.py
===================================================================
--- grok/trunk/src/grok/tests/container/orderedcontainer.py	                        (rev 0)
+++ grok/trunk/src/grok/tests/container/orderedcontainer.py	2008-05-01 14:03:05 UTC (rev 86001)
@@ -0,0 +1,78 @@
+"""
+
+The grok.OrderedContainer is a a model that is also a container. Unlike plain
+grok.Containers, OrderedContainers keep the mapping keys in the order items
+were added. It has a dictionary API. It in fact stores its information in a
+BTree so you can store a lot of items in a scalable way.
+
+  >>> grok.testing.grok(__name__)
+
+  >>> from zope.app.container.interfaces import IContainer
+  >>> bones = OrderedBones()
+  >>> IContainer.providedBy(bones)
+  True
+  >>> from zope.app.container.interfaces import IOrderedContainer
+  >>> IOrderedContainer.providedBy(bones)
+  True
+  >>> from zope.app.container.btree import BTreeContainer
+  >>> isinstance(bones, BTreeContainer)
+  True
+
+Order is initially determined by the sequence in which items were added (mind
+that there is a subscriber to the containermodified event in order to
+illustrate an ordered container fires events just like normal containers)::
+
+  >>> bones['thigh'] = Bone('Thigh Bone')
+  >>> bones['knee'] = Bone('Knee Cap')
+  >>> bones['shin'] = Bone('Shin Bone')
+  >>> bones['ankle'] = Bone('Ankle Joint')
+  >>> bones.keys()
+  ['thigh', 'knee', 'shin', 'ankle']
+
+Now change the order::
+
+  >>> bones.updateOrder(order=['ankle', 'shin', 'knee', 'thigh'])
+  >>> bones.keys()
+  ['ankle', 'shin', 'knee', 'thigh']
+
+  >>> list(bones.items())
+  [('ankle', <grok.tests.container.orderedcontainer.Bone object at ...>),
+  ('shin', <grok.tests.container.orderedcontainer.Bone object at ...>),
+  ('knee', <grok.tests.container.orderedcontainer.Bone object at ...>),
+  ('thigh', <grok.tests.container.orderedcontainer.Bone object at ...>)]
+
+  >>> [bone.name for bone in bones.values()]
+  ['Ankle Joint', 'Shin Bone', 'Knee Cap', 'Thigh Bone']
+
+  >>> del bones['knee']
+  >>> bones.keys()
+  ['ankle', 'shin', 'thigh']
+
+  >>> bones['toe'] = Bone('Toe')
+  >>> bones.keys()
+  ['ankle', 'shin', 'thigh', 'toe']
+
+Adding a new object under an existing key, raises a DuplicationError::
+
+  >>> bones['shin'] = Bone('Another Shin Bone')
+  Traceback (most recent call last):
+  ...
+  DuplicationError: shin
+
+Reordering with a wrong set of keys should fail::
+
+  >>> bones.updateOrder(order=['ankle', 'shin', 'knee', 'thigh'])
+  Traceback (most recent call last):
+  ...
+  ValueError: Incompatible key set.
+
+"""
+
+import grok
+
+class OrderedBones(grok.OrderedContainer):
+    pass
+
+class Bone(grok.Model):
+    def __init__(self, name):
+        self.name = name

Added: grok/trunk/src/grok/tests/container/orderedcontainerfiresevent.py
===================================================================
--- grok/trunk/src/grok/tests/container/orderedcontainerfiresevent.py	                        (rev 0)
+++ grok/trunk/src/grok/tests/container/orderedcontainerfiresevent.py	2008-05-01 14:03:05 UTC (rev 86001)
@@ -0,0 +1,39 @@
+"""
+Mind that there is a subscriber to the containermodified event in order to
+illustrate an ordered container fires events just like normal containers::
+
+  >>> grok.testing.grok(__name__)
+  >>> bones = OrderedBones()
+
+Add an item::
+
+  >>> bones['thigh'] = Bone('Thigh Bone')
+  Container has changed!
+
+Now change the order::
+
+  >>> bones.updateOrder(order=['thigh'])
+  Container has changed!
+
+Delete an item::
+
+  >>> del bones['thigh']
+  Container has changed!
+  >>> bones.keys()
+  []
+
+"""
+
+import grok
+
+class OrderedBones(grok.OrderedContainer):
+    pass
+
+class Bone(grok.Model):
+    def __init__(self, name):
+        self.name = name
+
+from zope.app.container.interfaces import IContainerModifiedEvent
+ at grok.subscribe(OrderedBones, IContainerModifiedEvent)
+def container_changed(object, event):
+    print 'Container has changed!'



More information about the Checkins mailing list