[Zope-dev] Re: default view

Florent Guillaume fg at nuxeo.com
Sat Jun 17 20:05:24 EDT 2006


Florent Guillaume wrote:
> So here's a proposal: how about having the following order:
> - __bobo_traverse__
> - unacquired attribute
> - zope 3 views
> - acquired attributes

Attached is the current diff I'm working with (for Zope 2.10).
Please review the unit test (which pass as is), to check if you agree 
with the semantics.

I've decided that if you traverse ".../foo/@@something", then only the 
zope 3 views will be consulted and never an attribute. I hope everyone 
agrees with that.

The remaining important question is: if a *default* view is specified 
using the zope 3 mechanism, should we always treat it as a zope 3 view, 
and refuse to lookup an attribute with that name?

Currently the traverse code is not completely coherent with respect to 
that, there's a NotFound in the tests I added which isn't coherent with 
the rest. So we should decide on one semantic.

To explicit the question, if you have:

     <browser:defaultView name="myview" for=".IFoo"/>

and the publisher decides it has to use the default view (when 
".../foo/" is traversed), should it try to lookup "myview" as an 
attribute? Or should only the zope 3 view be looked up? I'd be inclined 
to not use attributes, after all that's zope 3 directives we're talking 
about.

Florent

-- 
Florent Guillaume, Nuxeo (Paris, France)   Director of R&D
+33 1 40 33 71 59   http://nuxeo.com   fg at nuxeo.com
-------------- next part --------------
Index: lib/python/ZPublisher/tests/testBaseRequest.py
===================================================================
--- lib/python/ZPublisher/tests/testBaseRequest.py	(revision 68718)
+++ lib/python/ZPublisher/tests/testBaseRequest.py	(working copy)
@@ -3,8 +3,15 @@
 from Acquisition import Implicit
 from ZPublisher.BaseRequest import BaseRequest
 from ZPublisher.HTTPResponse import HTTPResponse
+from ZPublisher import NotFound
 
+import zope.interface
+import zope.testing.cleanup
+import zope.traversing.namespace
+from zope.app.testing import ztapi
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
 
+
 class DummyObjectBasic(Implicit):
     """Dummy class with docstring."""
 
@@ -166,7 +173,6 @@
 
     def test_traverse_withBDEmpty(self):
         # Collector 1079 (infinite loop 2)
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.f1.objWithBD._default_path = ['']
         self.failUnlessRaises(NotFound, r.traverse, 'folder/objWithBD')
@@ -174,7 +180,6 @@
     def test_traverse_withBBT_handles_AttributeError(self):
         # Test that if __bobo_traverse__ raises AttributeError
         # that we get a NotFound
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.failUnlessRaises(NotFound, r.traverse, 'folder/objWithBBT/bbt_foo')
 
@@ -194,7 +199,6 @@
         # and __bobo_traverse__
         # __bobo_traverse__ should raise an AttributeError, which will
         # raise a NotFound
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.f1.objWithBDBBT._default_path = ['xxx']
         r = self.makeBaseRequest()
@@ -214,27 +218,22 @@
         self.assertEqual(r.response.base, '')
 
     def test_traverse_attribute_without_docstring(self):
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.assertRaises(NotFound, r.traverse, 'folder/objBasic/noview')
 
     def test_traverse_class_without_docstring(self):
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.assertRaises(NotFound, r.traverse, 'folder/objWithoutDocstring')
 
     def test_traverse_attribute_of_class_without_docstring(self):
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.assertRaises(NotFound, r.traverse, 'folder/objWithoutDocstring/view')
 
     def test_traverse_attribute_and_class_without_docstring(self):
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.assertRaises(NotFound, r.traverse, 'folder/objWithoutDocstring/noview')
 
     def test_traverse_simple_type(self):
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.assertRaises(NotFound, r.traverse, 'folder/simpleString')
         self.assertRaises(NotFound, r.traverse, 'folder/simpleList')
@@ -242,14 +241,144 @@
         self.assertRaises(NotFound, r.traverse, 'folder/simpleComplex')
 
     def test_traverse_set_type(self):
-        from ZPublisher import NotFound
         r = self.makeBaseRequest()
         self.assertRaises(NotFound, r.traverse, 'folder/simpleSet')
         self.assertRaises(NotFound, r.traverse, 'folder/simpleFrozenSet')
 
 
+class IDummy(zope.interface.Interface):
+    """IDummy"""
+
+class DummyObjectBasicZ3(DummyObjectBasic):
+    zope.interface.implements(IDummy)
+    def __init__(self, name):
+        self.name = name
+
+class DummyObjectBasicZ3WithAttr(DummyObjectBasicZ3):
+    def meth(self):
+        """doc"""
+        return 'meth on %s' % self.name
+    def methonly(self):
+        """doc"""
+        return 'methonly on %s' % self.name
+
+class DummyView(Implicit):
+    def __init__(self, content, request):
+        self.content = content
+        self.request = request
+    def __call__(self):
+        return 'view on %s' % (self.content.name)
+
+class TestBaseRequestZope3Views(TestCase):
+
+    def setUp(self):
+        zope.testing.cleanup.cleanUp()
+        self.root = DummyObjectBasic()
+        self.f1 = self.root._setObject('folder', DummyObjectBasicZ3('folder'))
+        self.f1._setObject('obj', DummyObjectBasicZ3('obj'))
+        self.f1._setObject('withattr', DummyObjectBasicZ3WithAttr('withattr'))
+        self.f2 = self.root._setObject('folder2',
+                                       DummyObjectBasicZ3WithAttr('folder2'))
+        self.f2._setObject('obj2', DummyObjectBasicZ3('obj2'))
+        self.f2._setObject('withattr2', DummyObjectBasicZ3WithAttr('withattr2'))
+
+        # This is needed to make "zope 3" traversing work
+        zope.interface.classImplements(BaseRequest, IDefaultBrowserLayer)
+
+        # Define our 'meth' view
+        ztapi.browserView(IDummy, 'meth', DummyView)
+
+        # Bind @@ to the view namespace
+        ztapi.provideNamespaceHandler('view', zope.traversing.namespace.view)
+
+    def tearDown(self):
+        zope.testing.cleanup.cleanUp()
+
+    def makeBaseRequest(self):
+        response = HTTPResponse()
+        environment = { 'URL': '',
+                        'PARENTS': [self.root],
+                        'steps': [],
+                        '_hacked_path': 0,
+                        '_test_counter': 0,
+                        'response': response }
+        return BaseRequest(environment)
+
+    def test_traverse_view(self):
+        """simple view"""
+        r = self.makeBaseRequest()
+        ob = r.traverse('folder/obj/meth')
+        self.assertEqual(ob(), 'view on obj')
+        ob = r.traverse('folder/obj/@@meth')
+        self.assertEqual(ob(), 'view on obj')
+        # using default view
+        ztapi.setDefaultViewName(IDummy, 'meth')
+        ob = r.traverse('folder/obj/')
+        self.assertEqual(ob(), 'view on obj')
+        ob = r.traverse('folder/obj')
+        self.assertEqual(ob(), 'view on obj')
+
+    def test_traverse_view_attr_local(self):
+        """method on object used first"""
+        r = self.makeBaseRequest()
+        ob = r.traverse('folder/withattr/meth')
+        self.assertEqual(ob(), 'meth on withattr')
+        ob = r.traverse('folder/withattr/@@meth')
+        self.assertEqual(ob(), 'view on withattr')
+        # using default view
+        ztapi.setDefaultViewName(IDummy, 'meth')
+        ob = r.traverse('folder/withattr/')
+        self.assertEqual(ob(), 'meth on withattr')
+        ob = r.traverse('folder/withattr')
+        self.assertEqual(ob(), 'meth on withattr')
+
+    def test_traverse_view_attr_above(self):
+        """view takes precedence over acquired attribute"""
+        r = self.makeBaseRequest()
+        ob = r.traverse('folder2/obj2/meth')
+        self.assertEqual(ob(), 'view on obj2') # used to be buggy (acquired)
+        ob = r.traverse('folder2/obj2/@@meth')
+        self.assertEqual(ob(), 'view on obj2')
+        # using default view
+        ztapi.setDefaultViewName(IDummy, 'meth')
+        ob = r.traverse('folder2/obj2/')
+        self.assertEqual(ob(), 'view on obj2')
+        ob = r.traverse('folder2/obj2')
+        self.assertEqual(ob(), 'view on obj2')
+
+    def test_traverse_view_attr_local2(self):
+        """method with other method above"""
+        r = self.makeBaseRequest()
+        ob = r.traverse('folder2/withattr2/meth')
+        self.assertEqual(ob(), 'meth on withattr2')
+        ob = r.traverse('folder2/withattr2/@@meth')
+        self.assertEqual(ob(), 'view on withattr2')
+        # using default view
+        ztapi.setDefaultViewName(IDummy, 'meth')
+        ob = r.traverse('folder2/withattr2/')
+        self.assertEqual(ob(), 'meth on withattr2')
+        ob = r.traverse('folder2/withattr2')
+        self.assertEqual(ob(), 'meth on withattr2')
+
+    def test_traverse_view_attr_acquired(self):
+        """normal acquired attribute without view"""
+        r = self.makeBaseRequest()
+        ob = r.traverse('folder2/obj2/methonly')
+        self.assertEqual(ob(), 'methonly on folder2')
+        self.assertRaises(NotFound, r.traverse, 'folder2/obj2/@@methonly')
+        # using default view
+        ztapi.setDefaultViewName(IDummy, 'methonly')
+        ob = r.traverse('folder2/obj2/')
+        self.assertEqual(ob(), 'methonly on folder2')
+        ob = r.traverse('folder2/obj2')
+        self.assertEqual(ob(), 'methonly on folder2')
+
+
 def test_suite():
-    return TestSuite( ( makeSuite(TestBaseRequest), ) )
+    return TestSuite((
+        makeSuite(TestBaseRequest),
+        makeSuite(TestBaseRequestZope3Views),
+        ))
 
 if __name__ == '__main__':
     main(defaultTest='test_suite')
Index: lib/python/ZPublisher/BaseRequest.py
===================================================================
--- lib/python/ZPublisher/BaseRequest.py	(revision 68718)
+++ lib/python/ZPublisher/BaseRequest.py	(working copy)
@@ -17,6 +17,7 @@
 from urllib import quote
 import xmlrpc
 from zExceptions import Forbidden, Unauthorized, NotFound
+from Acquisition import aq_base
 
 from zope.interface import implements, providedBy, Interface
 from zope.component import queryMultiAdapter
@@ -72,6 +73,7 @@
             raise Forbidden("Object name begins with an underscore at: %s" % URL)
 
         try:
+            # 1. Try __bobo_traverse__
             if hasattr(object,'__bobo_traverse__'):
                 subobject=object.__bobo_traverse__(request, name)
                 if type(subobject) is type(()) and len(subobject) > 1:
@@ -81,24 +83,28 @@
                     object, subobject = subobject[-2:]
             else:
                 try:
+                    # 2. Try as attribute on base object
+                    getattr(aq_base(object), name)
                     subobject=getattr(object, name)
                 except AttributeError:
+                    # 2.5 Try as item
                     subobject=object[name]
-             
+
         except (AttributeError, KeyError, NotFound):
-            # Find a view even if it doesn't start with @@, but only
-            # If nothing else could be found
+            # 4. Try as view
             subobject = queryMultiAdapter((object, request), Interface, name)
             if subobject is not None:
                 # OFS.Application.__bobo_traverse__ calls
                 # REQUEST.RESPONSE.notFoundError which sets the HTTP
                 # status code to 404
-                request.RESPONSE.setStatus(200)
+                request.response.setStatus(200)
                 # We don't need to do the docstring security check
                 # for views, so lets skip it and return the object here.
                 return subobject.__of__(object) 
-            raise
 
+            # 5. Try as acquired attribute, may raise
+            subobject = getattr(object, name)
+
         # Ensure that the object has a docstring, or that the parent
         # object has a pseudo-docstring for the object. Objects that
         # have an empty or missing docstring are not published.


More information about the Zope-Dev mailing list