[z3-checkins] r36434 - in z3/sqlos/trunk/src/sqlos: . container ftests interfaces testing

kobold at codespeak.net kobold at codespeak.net
Wed Jan 10 20:31:38 CET 2007


Author: kobold
Date: Wed Jan 10 20:31:36 2007
New Revision: 36434

Added:
   z3/sqlos/trunk/src/sqlos/ftests/joins.txt
Modified:
   z3/sqlos/trunk/src/sqlos/configure.zcml
   z3/sqlos/trunk/src/sqlos/container/standard.py
   z3/sqlos/trunk/src/sqlos/ftesting.zcml
   z3/sqlos/trunk/src/sqlos/ftests/adding.txt
   z3/sqlos/trunk/src/sqlos/ftests/containers.txt
   z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt
   z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py
   z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py
   z3/sqlos/trunk/src/sqlos/interfaces/__init__.py
   z3/sqlos/trunk/src/sqlos/interfaces/container.py
   z3/sqlos/trunk/src/sqlos/testing/sampleperson.py
   z3/sqlos/trunk/src/sqlos/zsqlobject.py
Log:
Implemented native joins between SQLOS objects.


Modified: z3/sqlos/trunk/src/sqlos/configure.zcml
==============================================================================
--- z3/sqlos/trunk/src/sqlos/configure.zcml	(original)
+++ z3/sqlos/trunk/src/sqlos/configure.zcml	Wed Jan 10 20:31:36 2007
@@ -4,33 +4,7 @@
            xmlns:browser="http://namespaces.zope.org/browser"
            i18n_domain="sqlos">
 
-  <!-- ISQLObjectContainer Views -->
-
-  <class class=".container.SQLObjectContainer">
-
-    <implements interface="zope.app.container.interfaces.IContentContainer" />
-
-    <factory
-        id="sqlos.SQLObjectContainer"
-        title="SQLObject Container"
-        description="A container for SQLObject instances" />
-
-    <require
-        permission="zope.View"
-        interface="zope.app.container.interfaces.IReadContainer"
-        />
-
-    <require
-        permission="zope.ManageContent"
-        interface="zope.app.container.interfaces.IWriteContainer"
-        />
-
-    <require
-        permission="zope.View"
-        attributes="select"
-        />
-
-  </class>
+  <!-- Adapters -->
 
   <adapter
       provides="zope.app.container.interfaces.INameChooser"
@@ -45,8 +19,28 @@
       permission="zope.Public"
       factory=".container.SQLObjectMonoNameChooser"
       />
- 
-  <!-- Default view for containers. Should we really be specifying this??-->
+
+  <adapter
+      provides="zope.app.container.interfaces.INameChooser"
+      for="sqlos.interfaces.container.ISQLObjectJoinContainer"
+      permission="zope.Public"
+      factory=".container.SQLObjectMonoNameChooser"
+      />
+
+  <adapter
+      for="sqlos.interfaces.ISelectResults"
+      provides="sqlos.interfaces.container.ISQLObjectJoinContainer"
+      factory="sqlos.zsqlobject.SelectResultsContainer"
+      permission="zope.Public"
+      />
+
+  <adapter
+      for="sqlos.interfaces.ISQLSchema"
+      factory="sqlos.zsqlobject.SQLOSTraversable"
+      provides="zope.traversing.interfaces.ITraversable"
+      />
+      
+  <!-- Default view for containers -->
 
   <browser:defaultView
       for="sqlos.interfaces.container.ISQLObjectContainer"
@@ -62,6 +56,31 @@
       attribute="contents"
       />
 
+  <browser:defaultView
+      for="sqlos.interfaces.container.ISQLObjectJoinContainer"
+      name="contents.html"
+      />
+
+  <browser:page
+      name="contents.html"
+      menu="zmi_views" title="Contents"
+      for=".interfaces.container.ISQLObjectJoinContainer"
+      permission="zope.ManageContent"
+      class="zope.app.container.browser.contents.Contents"
+      attribute="contents"
+      />
+
+  <browser:page
+      name="contents.html"
+      menu="zmi_views" title="Contents"
+      for=".interfaces.ISQLObject"
+      permission="zope.ManageContent"
+      class="zope.app.container.browser.contents.Contents"
+      attribute="contents"
+      />
+
+  <!-- Database adapters -->
+
   <class class=".adapter.MySQLAdapter">
     <require
         permission="zope.Public"
@@ -131,6 +150,8 @@
         />
   </configure>
 
+  <!-- Subscribers for cache cleaning -->
+
   <subscriber
       for="zope.app.component.interfaces.ISite
            zope.app.publication.interfaces.IBeforeTraverseEvent"

Modified: z3/sqlos/trunk/src/sqlos/container/standard.py
==============================================================================
--- z3/sqlos/trunk/src/sqlos/container/standard.py	(original)
+++ z3/sqlos/trunk/src/sqlos/container/standard.py	Wed Jan 10 20:31:36 2007
@@ -23,7 +23,7 @@
 from zope.location.interfaces import ILocation
 from zope.proxy import sameProxiedObjects
 
-from sqlos.interfaces import ISQLObject, IISQLObject
+from sqlos.interfaces import ISQLSchema, ISQLObject, IISQLObject
 from sqlos.interfaces.container import ISQLObjectContainer
 
 
@@ -31,7 +31,7 @@
     """An implementation of zope.app.container.contained.contained
     that doesn't generate events, for internal use.
     """
-    if (parent is None):
+    if parent is None:
         raise TypeError, 'Must provide a parent'
 
     if not IContained.providedBy(obj):
@@ -40,16 +40,13 @@
         else:
             obj = ContainedProxy(obj)
 
-    oldparent = obj.__parent__
-    oldname = obj.__name__
+    obj.__name__ = unicode(name)
 
-    if (oldparent is None) or not (oldparent is parent
-                                   or sameProxiedObjects(oldparent, parent)):
+    if ISQLSchema.providedBy(obj):
+        obj.setParent(parent)
+    else:
         obj.__parent__ = parent
 
-    if oldname != name and name is not None:
-        obj.__name__ = unicode(name)
-
     return obj
 
 
@@ -165,7 +162,7 @@
            KeyError is raised.
         """
         try:
-            return contained(self[name], parent=self, name=name)
+            return self[name]
         except KeyError:
             return default
 

Modified: z3/sqlos/trunk/src/sqlos/ftesting.zcml
==============================================================================
--- z3/sqlos/trunk/src/sqlos/ftesting.zcml	(original)
+++ z3/sqlos/trunk/src/sqlos/ftesting.zcml	Wed Jan 10 20:31:36 2007
@@ -12,7 +12,7 @@
        You should only need to change the connectionName in order to
        test against a different database that sqlite -->
 
-  <sqlos:connectionName name='sqlite' />
+  <sqlos:connectionName name='pg2sql' />
 
   <adapter
       provides=".interfaces.IZopeSQLConnection"
@@ -89,6 +89,10 @@
         permission="zope.View"
         interface="sqlos.interfaces.ISQLObject"
         />
+    <require
+        permission="zope.View"
+        interface="zope.app.container.interfaces.IReadContainer"
+        />
   </class>
 
   <browser:addform
@@ -147,12 +151,17 @@
         permission="zope.View"
         interface="sqlos.interfaces.ISQLObject"
         />
+    <require
+        permission="zope.View"
+        interface="zope.app.container.interfaces.IReadContainer"
+        />
   </class>
 
   <browser:addform
       schema="sqlos.testing.sampleperson.IDog"
       content_factory="sqlos.testing.sampleperson.SampleDog"
-      keyword_arguments="fullname owner"
+      fields="fullname"
+      keyword_arguments="fullname"
       label="New Sample Dog"
       name="AddDog.html"
       permission="zope.ManageContent"
@@ -161,6 +170,7 @@
   <browser:editform
       schema="sqlos.testing.sampleperson.IDog"
       name="edit.html"
+      fields="fullname"
       menu="zmi_views"
       label="Edit a Sample Dog"
       permission="zope.ManageContent"

Modified: z3/sqlos/trunk/src/sqlos/ftests/adding.txt
==============================================================================
--- z3/sqlos/trunk/src/sqlos/ftests/adding.txt	(original)
+++ z3/sqlos/trunk/src/sqlos/ftests/adding.txt	Wed Jan 10 20:31:36 2007
@@ -139,4 +139,5 @@
     False
 
 CleanUp:
+
     >>> sampleperson.dropTestingTables()

Modified: z3/sqlos/trunk/src/sqlos/ftests/containers.txt
==============================================================================
--- z3/sqlos/trunk/src/sqlos/ftests/containers.txt	(original)
+++ z3/sqlos/trunk/src/sqlos/ftests/containers.txt	Wed Jan 10 20:31:36 2007
@@ -124,11 +124,15 @@
 
 Now we add sally's dog:
 
-    >>> fido = sampleperson.SampleDog(fullname='Fido', owner='sally')
+    >>> fido = sampleperson.SampleDog(fullname='Fido', owner=sally)
     >>> len(multicontainer)
     2
     >>> fido in [i for i in multicontainer.values()]
     True
+    >>> fido in sally.dogs
+    True
+    >>> fido.owner is sally
+    True
 
 CleanUp:
 

Added: z3/sqlos/trunk/src/sqlos/ftests/joins.txt
==============================================================================
--- (empty file)
+++ z3/sqlos/trunk/src/sqlos/ftests/joins.txt	Wed Jan 10 20:31:36 2007
@@ -0,0 +1,127 @@
+=================
+Joined SQLObjects
+=================
+
+Test join relationships between SQLOS objects.
+
+First lets set up some tables in the database:
+
+    >>> from sqlos.testing import sampleperson
+    >>> sampleperson.createTestingTables()
+
+Then get a browser:
+
+    >>> from zope.testbrowser.testing import Browser
+    >>> browser = Browser()
+    >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+    >>> browser.handleErrors = False
+
+Go to the main interface and add a SQLObject MultiContainer
+
+    >>> browser.open('http://localhost/manage')
+    >>> 'sqlos.testing.SamplePerson' not in str(browser.contents)
+    True
+    >>> browser.getLink('SQLObject Multi Container').click()
+    >>> browser.getControl(name='new_value').value = 'multicontainer1'
+    >>> browser.getControl('Apply').click()
+
+Now add a Sample Person to the container:
+
+    >>> browser.getLink('multicontainer1').click()
+    >>> browser.getLink('SamplePerson').click()
+    >>> browser.getControl(name='field.fullname').value = 'Bob'
+    >>> browser.getControl(name='field.username').value = 'bob'
+    >>> browser.getControl(name='field.password').value = 'obo'
+    >>> browser.getControl('Add').click()
+
+Lets try editing bob:
+
+    >>> browser.getLink('sqlos.testing.SamplePerson.1').click()
+    >>> print browser.contents
+    <!DOC...
+    ...
+    ...Full Name...
+    ...
+    ...Bob...
+    ...
+    ...Username...
+    ...
+    ...bob...
+    ...
+    ...Password...
+    ...
+    ...obo...
+    ...
+    >>> browser.getControl(name='field.fullname').value = 'Bobby Bones'
+    >>> browser.getControl('Change').click()
+    >>> print browser.contents
+    <!DOC...
+    ...
+    ...Updated on...
+    ...
+    ...Full Name...
+    ...
+    ...Bobby Bones...
+    ...
+
+Now, let's create a new Dog:
+
+    >>> from sqlos.testing.sampleperson import SamplePerson, SampleDog
+    >>> bob = SamplePerson.get(1)
+    >>> dog = SampleDog(fullname='Fido', owner=bob)
+
+Verify the interfaces:
+
+    >>> from zope.interface.verify import verifyObject
+    >>> from zope.app.container.interfaces import IReadContainer
+    >>> from sqlos.interfaces.container import ISQLObjectJoinContainer
+    >>> verifyObject(IReadContainer, bob)
+    True
+    >>> verifyObject(ISQLObjectJoinContainer, bob['dogs'])
+    True
+
+Let's try to access to the dog going through the owner:
+
+    >>> browser.getLink('Contents').click()
+    >>> 'dogs' in browser.contents
+    True
+    >>> browser.getLink('dogs').click()
+    >>> '1/@@' in browser.contents
+    True
+    >>> browser.open('http://localhost/multicontainer1/sqlos.testing.SamplePerson.1/'
+    ...     'dogs/1')
+    >>> browser.getControl(name='field.fullname').value = 'Fido dog'
+    >>> browser.getControl('Change').click()
+    >>> print browser.contents
+     <!DOC...
+    ...
+    ...Updated on...
+    ...
+    ...Full Name...
+    ...
+    ...Fido dog...
+    ...
+
+Let's try to access to the person going through the dog:
+
+    >>> browser.getLink('multicontainer1').click()
+    >>> browser.getLink('sqlos.testing.SampleDog.1').click()
+    >>> browser.getLink('Contents').click()
+    >>> 'owner' in browser.contents
+    True
+    >>> browser.getLink('owner').click()
+    >>> browser.getControl(name='field.fullname').value = 'Bob the top'
+    >>> browser.getControl('Change').click()
+    >>> print browser.contents
+    <!DOC...
+    ...
+    ...Updated on...
+    ...
+    ...Full Name...
+    ...
+    ...Bob the top...
+    ...
+
+CleanUp:
+
+    >>> sampleperson.dropTestingTables()

Modified: z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt
==============================================================================
--- z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt	(original)
+++ z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt	Wed Jan 10 20:31:36 2007
@@ -61,7 +61,7 @@
     ...         '''create table sample_dog (
     ...                   id integer primary key,
     ...                   fullname varchar(50) not null,
-    ...                   owner varchar(20) not null)''')
+    ...                   owner_id int not null)''')
     >>> c = cursor.execute(
     ...         '''create table sample_isolated_person (
     ...                   id integer primary key,

Modified: z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py
==============================================================================
--- z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py	(original)
+++ z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py	Wed Jan 10 20:31:36 2007
@@ -25,5 +25,6 @@
                 'containers.txt',
                 'localutilities.txt',
                 'isolated_containers.txt',
-                'mono_containers.txt']
+                'mono_containers.txt',
+                'joins.txt']
     return FunctionalDocFileSuite(*filelist)

Modified: z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py
==============================================================================
--- z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py	(original)
+++ z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py	Wed Jan 10 20:31:36 2007
@@ -20,7 +20,7 @@
 from zope.rdb.interfaces import IZopeDatabaseAdapter
 
 from sqlos.interfaces import IConnectionName
-from sqlos.testing.sampleperson import SamplePerson
+from sqlos.testing.sampleperson import SamplePerson, SampleDog
 
 __metaclass__ = type
 
@@ -29,6 +29,7 @@
     def setUp(self):
         super(TestTransaction, self).setUp()
         SamplePerson.createTable(ifNotExists=True)
+        SampleDog.createTable(ifNotExists=True)
         person = SamplePerson(fullname='Sidnei da Silva',
                               username='sidnei',
                               password='test')

Modified: z3/sqlos/trunk/src/sqlos/interfaces/__init__.py
==============================================================================
--- z3/sqlos/trunk/src/sqlos/interfaces/__init__.py	(original)
+++ z3/sqlos/trunk/src/sqlos/interfaces/__init__.py	Wed Jan 10 20:31:36 2007
@@ -14,7 +14,7 @@
 from zope.interface import classImplements
 from zope.security.checker import NamesChecker, NoProxy, defineChecker
 from zope.schema.vocabulary import SimpleVocabulary
-from zope.schema import Choice, List
+from zope.schema import Choice, List, TextLine
 from zope.annotation.interfaces import IAttributeAnnotatable
 from zope.app.container.interfaces import IContained
 from sqlobject import NoDefault
@@ -33,10 +33,13 @@
         )
 
 
-class ISQLSchema(Interface):
+class ISQLSchema(IContained):
     """Base interface for SQLObject-based objects"""
 
-    id = Attribute('Id')
+    id = Attribute(u'Id')
+
+    def setParent(parent):
+        """Set the object's parent"""
 
 
 class IDBConnection(Interface):
@@ -242,7 +245,9 @@
         """
 
 
-class ISQLObject(IContained):
+class ISQLObject(Interface):
+
+    sqlmeta = Attribute(u'Subclass sqlmeta for SQLObject objects')
 
     def set(**kw):
         """Used to update multiple values at once, potentially with one SQL
@@ -303,6 +308,63 @@
     def __len__():
         """List emulation"""
 
+    def clone(**newOps):
+        """"""
+
+    def orderBy(orderBy):
+        """"""
+
+    def connection(conn):
+        """"""
+
+    def limit(limit):
+        """"""
+
+    def lazyColumns(value):
+        """"""
+
+    def reversed():
+        """"""
+
+    def distinct():
+        """"""
+
+    def newClause(new_clause):
+        """"""
+
+    def filter(filter_clause):
+        """"""
+
+    def __getitem__(value):
+        """"""
+
+    def lazyIter():
+        """"""
+
+    def accumulate(*expressions):
+        """"""
+
+    def count():
+        """"""
+
+    def accumulateMany(*attributes):
+        """"""
+
+    def accumulateOne(func_name, attribute):
+        """"""
+
+    def sum(attribute):
+        """"""
+
+    def min(attribute):
+        """"""
+
+    def avg(attribute):
+        """"""
+
+    def max(attribute):
+        """"""
+
 
 class IIterator(Interface):
 

Modified: z3/sqlos/trunk/src/sqlos/interfaces/container.py
==============================================================================
--- z3/sqlos/trunk/src/sqlos/interfaces/container.py	(original)
+++ z3/sqlos/trunk/src/sqlos/interfaces/container.py	Wed Jan 10 20:31:36 2007
@@ -11,19 +11,17 @@
 """
 
 from zope.interface import Attribute
+from zope.interface.common.mapping import IEnumerableMapping
+
 from zope.deprecation import deprecated
 
 from zope.app.container.constraints import ItemTypePrecondition
 from zope.annotation.interfaces import IAttributeAnnotatable
-from zope.app.container.interfaces import IContainerNamesContainer
-from zope.app.container.interfaces import IReadContainer, IContainer
-
-
-class ISQLObjectReadContainer(IReadContainer, IAttributeAnnotatable):
-    """Read interface for SQLObject containers"""
+from zope.app.container.interfaces import IContainerNamesContainer, IReadContainer, \
+    IContainer, IContained
 
 
-class ISQLObjectContainer(IContainer, IContainerNamesContainer, IAttributeAnnotatable):
+class ISQLObjectContainer(IContainerNamesContainer, IAttributeAnnotatable):
     """A SQLObject container"""
 
     def __setitem__(name, obj):
@@ -45,6 +43,10 @@
     """A mono-type SQLObject container"""
 
 
+class ISQLObjectJoinContainer(IContainerNamesContainer, IEnumerableMapping, IContained):
+    """A SQLObject container for joins"""
+
+
 IIsolatedSQLContainer = ISQLObjectIsolatedContainer
 deprecated('IIsolatedSQLContainer', 'sqlos.interfaces.container.IIsolatedSQLContainer '
 'is deprecated and will go away in next release; use sqlos.interfaces.container.'

Modified: z3/sqlos/trunk/src/sqlos/testing/sampleperson.py
==============================================================================
--- z3/sqlos/trunk/src/sqlos/testing/sampleperson.py	(original)
+++ z3/sqlos/trunk/src/sqlos/testing/sampleperson.py	Wed Jan 10 20:31:36 2007
@@ -13,7 +13,7 @@
 from sqlobject import *
 import transaction
 
-from zope.interface import implements, classProvides, Interface
+from zope.interface import implements, classProvides, Interface, Attribute
 from zope.schema import TextLine, Datetime
 from zope.app.container import constraints
 from zope.app.container.interfaces import IContained
@@ -68,6 +68,8 @@
         required=True,
     )
 
+    dogs = Attribute(u'List of dogs')
+
 
 class IPersonContainer(ISQLObjectContainer):
 
@@ -97,6 +99,7 @@
     fullname = StringCol(length=50, notNull=1)
     username = StringCol(length=20, notNull=1)
     password = StringCol(length=20, notNull=1)
+    dogs = SQLMultipleJoin('SampleDog', joinColumn='owner_id')
 
 
 class SampleIsolatedPerson(SQLOS):
@@ -167,7 +170,7 @@
     implements(IDog)
 
     fullname = StringCol(length=50, notNull=1)
-    owner = StringCol(length=20, notNull=1)
+    owner = ForeignKey('SamplePerson', notNull=1, cascade=True)
 
 
 class IMultiContainer(IPersonContainer):

Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py
==============================================================================
--- z3/sqlos/trunk/src/sqlos/zsqlobject.py	(original)
+++ z3/sqlos/trunk/src/sqlos/zsqlobject.py	Wed Jan 10 20:31:36 2007
@@ -10,15 +10,25 @@
 $Id: __init__.py 5216 2004-06-21 18:33:07Z dreamcatcher $
 """
 
+import sqlobject
+
 from zope.interface import implements
 from sqlobject.main import SQLObject
-from sqlobject import StringCol
+
 from zope.app.container.contained import Contained
+from zope.app.container.interfaces import IReadContainer
+from zope.component import adapts
+from zope.security.proxy import removeSecurityProxy
+from zope.traversing.interfaces import ITraversable
+from zope.traversing.adapters import _marker
 
 from sqlos.connection import ConnectionDescriptor
-from sqlos.interfaces import ISQLObject
+from sqlos.container import contained
+from sqlos.interfaces import ISQLObject, ISelectResults
+from sqlos.interfaces.container import ISQLObjectJoinContainer
 from sqlos._transaction import dirty_object_registry
 
+
 def syncUpdateAll():
     """Calls syncUpdate on all dirty SQLOS objects, sending all SQL to the DB.
 
@@ -46,6 +56,9 @@
         >>> from zope.interface.verify import verifyObject
         >>> verifyObject(ISQLObject, s)
         True
+        >>> verifyObject(IReadContainer, s)
+        True
+
 
     And finally call tearDown and cleanup:
 
@@ -53,9 +66,10 @@
 
     """
 
-    implements(ISQLObject)
+    implements(ISQLObject, IReadContainer)
 
     _connection = ConnectionDescriptor()
+    _containers = None
 
     class sqlmeta:
         lazyUpdate = True
@@ -69,19 +83,11 @@
     dirty = property(_get_dirty, _set_dirty)
 
     def get(self, id, connection=None, selectResults=None):
-        # While interacting with zope, we may end up having
-        # objects in the cache that have a __parent__ set.
-        # This may be confusing when expect to get a object
-        # which has no __parent__ and thats not what you get.
         try:
-            val = super(SQLOS, self).get(id, connection=connection,
+            return super(SQLOS, self).get(id, connection=connection,
                 selectResults=selectResults)
         except ValueError:
             raise AttributeError, id
-        if getattr(val, '__parent__', None) is not None:
-            val.__parent__ = None
-            val.__name__ = None
-        return val
     get = classmethod(get)
 
     def __repr__(self):
@@ -91,3 +97,162 @@
         if connection is not None:
             self._connection = connection
     setConnection = classmethod(setConnection)
+
+    def setParent(self, parent):
+        self.__parent__ = parent
+
+    def __getitem__(self, name):
+        """See zope.app.container.interfaces.IReadContainer"""
+        if self._containers is None:
+            self._containers = {}
+        elif name in self._containers:
+            return self._containers[name]
+
+        obj = None
+        sqlmeta = self.sqlmeta
+        for column in sqlmeta.columns:
+            if column.endswith('ID') and \
+               sqlmeta.columns[column].foreignName == name and \
+               isinstance(sqlmeta.columns[column], sqlobject.SOForeignKey):
+                obj = contained(getattr(self, sqlmeta.columns[column].foreignName),
+                    parent=self, name=name)
+
+        if obj is not None:
+            self._containers[name] = obj
+            return obj
+
+        for j in sqlmeta.joins:
+            if j.joinDef.name == name:
+                obj = getattr(self, name)
+                if ISelectResults.providedBy(obj):
+                    obj = contained(ISQLObjectJoinContainer(obj), parent=self, name=name)
+                elif not ISQLObject.providedBy(obj):
+                    obj = None
+
+        if obj is not None:
+            self._containers[name] = obj
+            return obj
+
+        raise KeyError, name
+
+    def __contains__(self, name):
+        """See zope.app.container.interfaces.IReadContainer"""
+        return name in self.values()
+
+    def __iter__(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        for i in self.keys():
+            yield i
+
+    def __len__(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        return len(tuple(self.keys()))
+
+    def keys(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        sqlmeta = self.sqlmeta
+        for column in sqlmeta.columns:
+            if column.endswith('ID') and \
+               isinstance(sqlmeta.columns[column], sqlobject.SOForeignKey):
+                yield sqlmeta.columns[column].foreignName
+        for join in sqlmeta.joins:
+            yield join.joinDef.name
+
+    def items(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        for key in self.keys():
+            yield (key, self[key])
+
+    def values(self):
+        """See zope.app.container.interfaces.IReadContainer"""
+        for key in self.keys():
+            yield self[key]
+
+
+class SQLOSTraversable(object):
+    """Traverses objects via item lookup and attribute"""
+
+    implements(ITraversable)
+
+    def __init__(self, subject):
+        self._subject = subject
+
+    def traverse(self, name, furtherPath):
+        subject = self._subject
+        __traceback_info__ = (subject, name, furtherPath)
+        if hasattr(subject, '__getitem__'):
+            try:
+                return subject[name]
+            except KeyError:
+                pass
+        attr = getattr(subject, name, _marker)
+        if attr is not _marker:
+            return attr
+        raise TraversalError(subject, name)
+
+
+class SelectResultsContainer:
+    """Adapter for SelectResults objects"""
+
+    adapts(ISelectResults)
+
+    implements(ISQLObjectJoinContainer)
+
+    def __init__(self, context):
+        self.context = context
+        self.base_class = None
+
+    def __len__(self):
+        return int(self.context.count())
+
+    def __getitem__(self, name):
+        if not self.base_class:
+            sqlmeta = removeSecurityProxy(self.__parent__.sqlmeta)
+            join = filter(lambda x: x.joinDef.name == self.__name__, sqlmeta.joins)[0]
+            self.base_class = join.otherClass
+        try:
+            result = list(self.context.filter(
+                self.base_class.q.id == self.base_class.sqlmeta.idType(name)))
+            if result:
+                return contained(result[0], name=unicode(name), parent=self)
+        except ValueError:
+            pass
+        raise KeyError, name
+
+    def __iter__(self):
+        for j in self.context:
+            yield unicode(j.id)
+
+    def keys(self):
+        return [unicode(j.id) for j in self.context]
+
+    def items(self):
+        for j in self.context:
+            yield unicode(j.id), contained(j, name=unicode(j.id), parent=self)
+
+    def values(self):
+        for j in self.context:
+            yield contained(j, name=unicode(j.id), parent=self)
+
+    def __nonzero__(self):
+        return len(self)
+
+    def get(self, name, default=None):
+        try:
+            return self[name]
+        except KeyError:
+            return default
+
+    def __contains__(self, object):
+        return object in list(self.items())
+
+    def __delitem__(self, name):
+        """Delete the named object from the container.
+
+        Raises a KeyError if the object is not found.
+        """
+        obj = self[name]
+        obj.destroySelf()
+
+    def __setitem__(self, name, content):
+        return name


More information about the z3-checkins mailing list