• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Zope / 3956162881

pending completion
3956162881

push

github

Michael Howitz
Update to deprecation warning free releases.

4401 of 7036 branches covered (62.55%)

Branch coverage included in aggregate %.

27161 of 31488 relevant lines covered (86.26%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.58
/src/OFS/tests/testTraverse.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
#
12
##############################################################################
13
"""Traverse unit tests.
1✔
14
"""
15

16
import unittest
1✔
17

18

19
class UnitTestSecurityPolicy:
1✔
20
    """
21
        Stub out the existing security policy for unit testing purposes.
22
    """
23
    #   Standard SecurityPolicy interface
24
    def validate(self, accessed=None, container=None, name=None, value=None,
1✔
25
                 context=None, roles=None, *args, **kw):
26
        return 1
1✔
27

28
    def checkPermission(self, permission, object, context):
1✔
29
        return 1
×
30

31

32
class CruelSecurityPolicy:
1✔
33
    """Denies everything
34
    """
35
    #   Standard SecurityPolicy interface
36
    def validate(self, accessed, container, name, value, *args):
1✔
37
        from AccessControl import Unauthorized
1✔
38
        raise Unauthorized(name)
1✔
39

40
    def checkPermission(self, permission, object, context):
1✔
41
        return 0
×
42

43

44
class ProtectedMethodSecurityPolicy:
1✔
45
    """Check security strictly on bound methods.
46
    """
47
    def validate(self, accessed, container, name, value, *args):
1✔
48
        from AccessControl import Unauthorized
1✔
49
        from Acquisition import aq_base
1✔
50
        if getattr(aq_base(value), '__self__', None) is None:
1!
51
            return 1
×
52

53
        # Bound method
54
        if name is None:
1!
55
            raise Unauthorized
×
56
        klass = value.__self__.__class__
1✔
57
        roles = getattr(klass, name + '__roles__', object())
1✔
58
        if roles is None:  # ACCESS_PUBLIC
1✔
59
            return 1
1✔
60

61
        raise Unauthorized(name)
1✔
62

63

64
class TestTraverse(unittest.TestCase):
1✔
65

66
    def setUp(self):
1✔
67
        import io
1✔
68

69
        import transaction
1✔
70
        from AccessControl import SecurityManager
1✔
71
        from AccessControl.SecurityManagement import newSecurityManager
1✔
72
        from OFS.Application import Application
1✔
73
        from OFS.Folder import manage_addFolder
1✔
74
        from OFS.Image import manage_addFile
1✔
75
        from Testing.makerequest import makerequest
1✔
76
        from ZODB.DB import DB
1✔
77
        from ZODB.DemoStorage import DemoStorage
1✔
78

79
        s = DemoStorage()
1✔
80
        self.connection = DB(s).open()
1✔
81

82
        try:
1✔
83
            r = self.connection.root()
1✔
84
            a = Application()
1✔
85
            r['Application'] = a
1✔
86
            self.root = a
1✔
87
            responseOut = self.responseOut = io.BytesIO()
1✔
88
            self.app = makerequest(self.root, stdout=responseOut)
1✔
89
            manage_addFolder(self.app, 'folder1')
1✔
90
            folder1 = getattr(self.app, 'folder1')
1✔
91
            setattr(folder1, '+something', 'plus')
1✔
92

93
            folder1.all_meta_types = (
1✔
94
                {'name': 'File',
95
                 'action': 'manage_addFile',
96
                 'permission': 'Add images and files'
97
                 },
98
            )
99

100
            manage_addFile(folder1, 'file',
1✔
101
                           file=b'', content_type='text/plain')
102

103
            # Hack, we need a _p_mtime for the file, so we make sure that it
104
            # has one. We use a subtransaction, which means we can rollback
105
            # later and pretend we didn't touch the ZODB.
106
            transaction.commit()
1✔
107
        except Exception:
×
108
            self.connection.close()
×
109
            raise
×
110
        transaction.begin()
1✔
111
        self.folder1 = getattr(self.app, 'folder1')
1✔
112

113
        self.policy = UnitTestSecurityPolicy()
1✔
114
        self.oldPolicy = SecurityManager.setSecurityPolicy(self.policy)
1✔
115
        newSecurityManager(None, self._makeUser().__of__(self.root))
1✔
116

117
    def tearDown(self):
1✔
118
        import transaction
1✔
119
        self._setupSecurity()
1✔
120
        del self.oldPolicy
1✔
121
        del self.policy
1✔
122
        del self.folder1
1✔
123
        transaction.abort()
1✔
124
        self.app._p_jar.sync()
1✔
125
        self.connection.close()
1✔
126
        del self.app
1✔
127
        del self.responseOut
1✔
128
        del self.root
1✔
129
        del self.connection
1✔
130

131
    def _makeUser(self):
1✔
132
        from Acquisition import Implicit
1✔
133

134
        class UnitTestUser(Implicit):
1✔
135
            """
136
                Stubbed out manager for unit testing purposes.
137
            """
138
            def getId(self):
1✔
139
                return 'unit_tester'
1✔
140
            getUserName = getId
1✔
141

142
            def allowed(self, object, object_roles=None):
1✔
143
                return 1
×
144

145
        return UnitTestUser()
1✔
146

147
    def _makeBoboTraversable(self):
1✔
148
        from OFS.SimpleItem import SimpleItem
1✔
149

150
        class BoboTraversable(SimpleItem):
1✔
151
            __allow_access_to_unprotected_subobjects__ = 1
1✔
152

153
            def __bobo_traverse__(self, request, name):
1✔
154
                if name == 'bb_subitem':
1✔
155
                    return BoboTraversable().__of__(self)
1✔
156
                elif name == 'bb_method':
1✔
157
                    return self.bb_method
1✔
158
                elif name == 'bb_status':
1✔
159
                    return self.bb_status
1✔
160
                elif name == 'manufactured':
1✔
161
                    return 42
1✔
162
                else:
163
                    raise KeyError
1✔
164

165
            def bb_method(self):
1✔
166
                """Test Method"""
167
                pass
×
168

169
            bb_status = 'screechy'
1✔
170

171
        return BoboTraversable()
1✔
172

173
    def _makeBoboTraversableWithAcquisition(self):
1✔
174
        from OFS.SimpleItem import SimpleItem
1✔
175

176
        class BoboTraversableWithAcquisition(SimpleItem):
1✔
177
            """ A BoboTraversable which may use acquisition to find objects.
178

179
            This is similar to how the __bobo_traverse__ behaves).
180
            """
181

182
            def __bobo_traverse__(self, request, name):
1✔
183
                from Acquisition import aq_get
1✔
184
                return aq_get(self, name)
1✔
185

186
        return BoboTraversableWithAcquisition()
1✔
187

188
    def _makeRestricted(self, name='dummy'):
1✔
189
        from OFS.SimpleItem import SimpleItem
1✔
190

191
        class Restricted(SimpleItem):
1✔
192
            """Instance we'll check with ProtectedMethodSecurityPolicy."""
193

194
            getId__roles__ = None  # ACCESS_PUBLIC
1✔
195
            def getId(self):  # NOQA: E306  # pseudo decorator
1✔
196
                return self.id
1✔
197

198
            private__roles__ = ()  # ACCESS_PRIVATE
1✔
199
            def private(self):  # NOQA: E306  # pseudo decorator
1✔
200
                return 'private!'
×
201

202
            # not protected
203
            def ohno(self):
1✔
204
                return 'ohno!'
×
205

206
        return Restricted(name)
1✔
207

208
    def _setupSecurity(self, policy=None):
1✔
209
        from AccessControl import SecurityManager
1✔
210
        from AccessControl.SecurityManagement import noSecurityManager
1✔
211
        if policy is None:
1✔
212
            policy = self.oldPolicy
1✔
213
        noSecurityManager()
1✔
214
        SecurityManager.setSecurityPolicy(policy)
1✔
215

216
    def test_interfaces(self):
1✔
217
        from OFS.interfaces import ITraversable
1✔
218
        from OFS.Traversable import Traversable
1✔
219
        from zope.interface.verify import verifyClass
1✔
220

221
        verifyClass(ITraversable, Traversable)
1✔
222

223
    def testTraversePath(self):
1✔
224
        self.assertTrue('file' in self.folder1.objectIds())
1✔
225
        self.assertTrue(
1✔
226
            self.folder1.unrestrictedTraverse(('', 'folder1', 'file')))
227
        self.assertTrue(self.folder1.unrestrictedTraverse(('', 'folder1')))
1✔
228

229
    def testTraverseURLNoSlash(self):
1✔
230
        self.assertTrue('file' in self.folder1.objectIds())
1✔
231
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/file'))
1✔
232
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1'))
1✔
233

234
    def testTraverseURLSlash(self):
1✔
235
        self.assertTrue('file' in self.folder1.objectIds())
1✔
236
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/file/'))
1✔
237
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/'))
1✔
238

239
    def testTraverseToNone(self):
1✔
240
        self.assertRaises(
1✔
241
            KeyError,
242
            self.folder1.unrestrictedTraverse, ('', 'folder1', 'file2'))
243
        self.assertRaises(
1✔
244
            KeyError, self.folder1.unrestrictedTraverse, '/folder1/file2')
245
        self.assertRaises(
1✔
246
            KeyError, self.folder1.unrestrictedTraverse, '/folder1/file2/')
247

248
    def testTraverseMethodRestricted(self):
1✔
249
        from AccessControl import Unauthorized
1✔
250
        self.root.my = self._makeRestricted('my')
1✔
251
        my = self.root.my
1✔
252
        my.id = 'my'
1✔
253
        self._setupSecurity(ProtectedMethodSecurityPolicy())
1✔
254
        r = my.restrictedTraverse('getId')
1✔
255
        self.assertEqual(r(), 'my')
1✔
256
        self.assertRaises(Unauthorized, my.restrictedTraverse, 'private')
1✔
257
        self.assertRaises(Unauthorized, my.restrictedTraverse, 'ohno')
1✔
258

259
    def testBoboTraverseToWrappedSubObj(self):
1✔
260
        # Verify it's possible to use __bobo_traverse__ with the
261
        # Zope security policy.
262
        self._setupSecurity()
1✔
263
        bb = self._makeBoboTraversable()
1✔
264
        self.assertRaises(KeyError, bb.restrictedTraverse, 'notfound')
1✔
265
        bb.restrictedTraverse('bb_subitem')
1✔
266

267
    def testBoboTraverseToMethod(self):
1✔
268
        # Verify it's possible to use __bobo_traverse__ to a method.
269
        self._setupSecurity()
1✔
270
        bb = self._makeBoboTraversable()
1✔
271
        self.assertTrue(
1✔
272
            bb.restrictedTraverse('bb_method') is not bb.bb_method)
273

274
    def testBoboTraverseToSimpleAttrValue(self):
1✔
275
        # Verify it's possible to use __bobo_traverse__ to a simple
276
        # python value
277
        self._setupSecurity()
1✔
278
        bb = self._makeBoboTraversable()
1✔
279
        self.assertEqual(bb.restrictedTraverse('bb_status'), 'screechy')
1✔
280

281
    def testBoboTraverseToNonAttrValue(self):
1✔
282
        # Verify it's possible to use __bobo_traverse__ to an
283
        # arbitrary manufactured object
284
        # Default security policy always seems to deny in this case, which
285
        # is fine, but to test the code branch we sub in the forgiving one
286
        self._setupSecurity(UnitTestSecurityPolicy())
1✔
287
        bb = self._makeBoboTraversable()
1✔
288
        self.assertTrue(
1✔
289
            bb.restrictedTraverse('manufactured') == 42)
290

291
    def testBoboTraverseToAcquiredObject(self):
1✔
292
        # Verify it's possible to use a __bobo_traverse__ which retrieves
293
        # objects by acquisition
294
        from Acquisition import aq_inner
1✔
295
        self._setupSecurity()
1✔
296
        bb = self._makeBoboTraversableWithAcquisition()
1✔
297
        bb = bb.__of__(self.root)
1✔
298
        self.assertEqual(
1✔
299
            bb.restrictedTraverse('folder1'), bb.folder1)
300
        self.assertEqual(
1✔
301
            aq_inner(bb.restrictedTraverse('folder1')),
302
            self.root.folder1)
303

304
    def testBoboTraverseToAcquiredProtectedObject(self):
1✔
305
        # Verify it's possible to use a __bobo_traverse__ which retrieves
306
        # objects by acquisition
307
        from AccessControl import Unauthorized
1✔
308
        from AccessControl.Permissions import access_contents_information
1✔
309
        self._setupSecurity()
1✔
310
        folder = self.root.folder1
1✔
311
        # restrict the ability to access the retrieved object itself
312
        folder.manage_permission(access_contents_information, [], 0)
1✔
313
        bb = self._makeBoboTraversableWithAcquisition()
1✔
314
        bb = bb.__of__(self.root)
1✔
315
        self.assertRaises(Unauthorized,
1✔
316
                          bb.restrictedTraverse, 'folder1')
317

318
    def testBoboTraverseToAcquiredAttribute(self):
1✔
319
        # Verify it's possible to use __bobo_traverse__ to an acquired
320
        # attribute
321
        self._setupSecurity()
1✔
322
        folder = self.root.folder1
1✔
323
        folder.stuff = 'stuff here'
1✔
324
        bb = self._makeBoboTraversableWithAcquisition()
1✔
325
        bb = bb.__of__(folder)
1✔
326
        self.assertEqual(
1✔
327
            bb.restrictedTraverse('stuff'), 'stuff here')
328

329
    def testBoboTraverseToAcquiredProtectedAttribute(self):
1✔
330
        # Verify that using __bobo_traverse__ to get an acquired but
331
        # protected attribute results in Unauthorized
332
        from AccessControl import Unauthorized
1✔
333
        from AccessControl.Permissions import access_contents_information
1✔
334
        self._setupSecurity()
1✔
335
        folder = self.root.folder1
1✔
336
        # We protect the the attribute by restricting access to the parent
337
        folder.manage_permission(access_contents_information, [], 0)
1✔
338
        folder.stuff = 'stuff here'
1✔
339
        bb = self._makeBoboTraversableWithAcquisition()
1✔
340
        bb = bb.__of__(folder)
1✔
341
        self.assertRaises(Unauthorized,
1✔
342
                          self.root.folder1.restrictedTraverse, 'stuff')
343

344
    def testBoboTraverseTraversalDefault(self):
1✔
345
        from OFS.SimpleItem import SimpleItem
1✔
346
        from ZPublisher.interfaces import UseTraversalDefault
1✔
347

348
        class BoboTraversableUseTraversalDefault(SimpleItem):
1✔
349
            """
350
              A BoboTraversable class which may use "UseTraversalDefault"
351
              (dependent on "name") to indicate that standard traversal should
352
              be used.
353
            """
354
            default = 'Default'
1✔
355

356
            def __bobo_traverse__(self, request, name):
1✔
357
                if name == 'normal':
1✔
358
                    return 'Normal'
1✔
359
                raise UseTraversalDefault
1✔
360

361
        bb = BoboTraversableUseTraversalDefault()
1✔
362
        # normal access -- no traversal default used
363
        self.assertEqual(bb.unrestrictedTraverse('normal'), 'Normal')
1✔
364
        # use traversal default
365
        self.assertEqual(bb.unrestrictedTraverse('default'), 'Default')
1✔
366
        # test traversal default with acqires attribute
367
        si = SimpleItem()
1✔
368
        si.default_acquire = 'Default_Acquire'
1✔
369
        si.bb = bb
1✔
370
        self.assertEqual(si.unrestrictedTraverse('bb/default_acquire'),
1✔
371
                         'Default_Acquire')
372

373
    def testAcquiredAttributeDenial(self):
1✔
374
        # Verify that restrictedTraverse raises the right kind of exception
375
        # on denial of access to an acquired attribute.  If it raises
376
        # AttributeError instead of Unauthorized, the user may never
377
        # be prompted for HTTP credentials.
378
        from AccessControl import Unauthorized
1✔
379
        from AccessControl.SecurityManagement import newSecurityManager
1✔
380
        self._setupSecurity(CruelSecurityPolicy())
1✔
381
        newSecurityManager(None, self._makeUser().__of__(self.root))
1✔
382
        self.root.stuff = 'stuff here'
1✔
383
        self.assertRaises(Unauthorized,
1✔
384
                          self.app.folder1.restrictedTraverse, 'stuff')
385

386
    def testDefaultValueWhenUnathorized(self):
1✔
387
        # Test that traversing to an unauthorized object returns
388
        # the default when provided
389
        from AccessControl.SecurityManagement import newSecurityManager
1✔
390
        self._setupSecurity(CruelSecurityPolicy())
1✔
391
        newSecurityManager(None, self._makeUser().__of__(self.root))
1✔
392
        self.root.stuff = 'stuff here'
1✔
393
        self.assertEqual(
1✔
394
            self.root.folder1.restrictedTraverse('stuff', 42), 42)
395

396
    def testNotFoundIsRaised(self):
1✔
397
        from operator import getitem
1✔
398

399
        from OFS.SimpleItem import SimpleItem
1✔
400
        from zExceptions import NotFound
1✔
401
        self.folder1._setObject('foo', SimpleItem('foo'))
1✔
402
        self.assertRaises(AttributeError, getitem, self.folder1.foo,
1✔
403
                          'doesntexist')
404
        self.assertRaises(NotFound, self.folder1.unrestrictedTraverse,
1✔
405
                          'foo/doesntexist')
406
        self.assertRaises(TypeError, getitem,
1✔
407
                          self.folder1.foo.isPrincipiaFolderish, 'doesntexist')
408
        self.assertRaises(NotFound, self.folder1.unrestrictedTraverse,
1✔
409
                          'foo/isPrincipiaFolderish/doesntexist')
410

411
    def testDefaultValueWhenNotFound(self):
1✔
412
        # Test that traversing to a non-existent object returns
413
        # the default when provided
414
        self._setupSecurity()
1✔
415
        self.assertEqual(
1✔
416
            self.root.restrictedTraverse('happy/happy', 'joy'), 'joy')
417

418
    def testTraverseUp(self):
1✔
419
        # Test that we can traverse upwards
420
        from Acquisition import aq_base
1✔
421
        self.assertTrue(
1✔
422
            aq_base(self.root.folder1.file.restrictedTraverse('../..')) is
423
            aq_base(self.root))
424

425
    def testTraverseToNameStartingWithPlus(self):
1✔
426
        # Verify it's possible to traverse to a name such as +something
427
        self.assertTrue(
1✔
428
            self.folder1.unrestrictedTraverse('+something') == 'plus')
429

430
    def testTraverseWrongType(self):
1✔
431
        with self.assertRaises(TypeError):
1✔
432
            self.folder1.unrestrictedTraverse(1)
1✔
433
        with self.assertRaises(TypeError):
1✔
434
            self.folder1.unrestrictedTraverse(b"foo")
1✔
435
        with self.assertRaises(TypeError):
1✔
436
            self.folder1.unrestrictedTraverse(["foo", b"bar"])
1✔
437
        with self.assertRaises(TypeError):
1✔
438
            self.folder1.unrestrictedTraverse(("foo", None))
1✔
439
        with self.assertRaises(TypeError):
1✔
440
            self.folder1.unrestrictedTraverse({1, "foo"})
1✔
441

442
    def testTraverseEmptyPath(self):
1✔
443
        self.assertEqual(self.folder1.unrestrictedTraverse(None), self.folder1)
1✔
444
        self.assertEqual(self.folder1.unrestrictedTraverse(""), self.folder1)
1✔
445
        self.assertEqual(self.folder1.unrestrictedTraverse([]), self.folder1)
1✔
446
        self.assertEqual(self.folder1.unrestrictedTraverse({}), self.folder1)
1✔
447

448

449
class SimpleClass:
1✔
450
    """Class with no __bobo_traverse__."""
451

452

453
def test_traversable():
1✔
454
    """
455
    Test the behaviour of unrestrictedTraverse and views. The tests don't
456
    use publishing but do unrestrictedTraverse instead.
457

458
      >>> import Products.Five
459
      >>> from Zope2.App import zcml
460
      >>> zcml.load_config("configure.zcml", Products.Five)
461
      >>> from Testing.makerequest import makerequest
462
      >>> self.app = makerequest(self.app)  # NOQA: F821
463
      >>> folder = self.folder  # NOQA: F821
464

465
    ``SimpleContent`` is a traversable class by default.  Its fallback
466
    traverser should raise NotFound when traversal fails.  (Note: If
467
    we return None in __fallback_traverse__, this test passes but for
468
    the wrong reason: None doesn't have a docstring so BaseRequest
469
    raises NotFoundError.)
470

471
      >>> from Products.Five.tests.testing import simplecontent
472
      >>> simplecontent.manage_addSimpleContent(folder, 'testoid', 'Testoid')
473
      >>> from zExceptions import NotFound
474
      >>> try:
475
      ...    folder.testoid.unrestrictedTraverse('doesntexist')
476
      ... except NotFound:
477
      ...    pass
478

479
    Now let's take class which already has a __bobo_traverse__ method.
480
    We should correctly use that as a fallback.
481

482
      >>> configure_zcml = '''
483
      ... <configure xmlns="http://namespaces.zope.org/zope"
484
      ...            xmlns:meta="http://namespaces.zope.org/meta"
485
      ...            xmlns:browser="http://namespaces.zope.org/browser"
486
      ...            xmlns:five="http://namespaces.zope.org/five">
487
      ...
488
      ... <!-- make the zope2.Public permission work -->
489
      ... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
490
      ...
491
      ... <!-- this view will never be found -->
492
      ... <browser:page
493
      ...     for="Products.Five.tests.testing.fancycontent.IFancyContent"
494
      ...     class="Products.Five.browser.tests.pages.FancyView"
495
      ...     attribute="view"
496
      ...     name="fancyview"
497
      ...     permission="zope2.Public"
498
      ...     />
499
      ... <!-- these two will -->
500
      ... <browser:page
501
      ...     for="Products.Five.tests.testing.fancycontent.IFancyContent"
502
      ...     class="Products.Five.browser.tests.pages.FancyView"
503
      ...     attribute="view"
504
      ...     name="raise-attributeerror"
505
      ...     permission="zope2.Public"
506
      ...     />
507
      ... <browser:page
508
      ...     for="Products.Five.tests.testing.fancycontent.IFancyContent"
509
      ...     class="Products.Five.browser.tests.pages.FancyView"
510
      ...     attribute="view"
511
      ...     name="raise-keyerror"
512
      ...     permission="zope2.Public"
513
      ...     />
514
      ... <!-- an item that can be traversed to via adaptation -->
515
      ... <browser:page
516
      ...     for="*"
517
      ...     class="Products.Five.tests.testing.fancycontent.FancyContent"
518
      ...     name="acquirer"
519
      ...     permission="zope2.Public"
520
      ...     />
521
      ... </configure>'''
522
      >>> zcml.load_string(configure_zcml)
523

524
      >>> from Products.Five.tests.testing import fancycontent
525
      >>> info = fancycontent.manage_addFancyContent(folder, 'fancy', '')
526

527
    In the following test we let the original __bobo_traverse__ method
528
    kick in:
529

530
      >>> folder.fancy.unrestrictedTraverse('something-else'
531
      ...                                       ).index_html({})
532
      'something-else'
533

534
    Once we have a custom __bobo_traverse__ method, though, it always
535
    takes over.  Therefore, unless it raises AttributeError or
536
    KeyError, it will be the only way traversal is done.
537

538
      >>> folder.fancy.unrestrictedTraverse('fancyview').index_html({})
539
      'fancyview'
540

541
    Note that during publishing, if the original __bobo_traverse__ method
542
    *does* raise AttributeError or KeyError, we can get normal view look-up.
543
    In unrestrictedTraverse, we don't. Maybe we should? Needs discussing.
544

545
      >>> folder.fancy.unrestrictedTraverse(
546
      ...     'raise-attributeerror')() == 'Fancy, fancy'
547
      True
548

549
      >>> folder.fancy.unrestrictedTraverse(
550
      ...     'raise-keyerror')() == 'Fancy, fancy'
551
      True
552

553
      >>> try:
554
      ...     folder.fancy.unrestrictedTraverse('raise-valueerror')
555
      ... except ValueError:
556
      ...     pass
557

558
    In the Zope 2 ZPublisher, an object with a __bobo_traverse__ will not do
559
    attribute lookup unless the __bobo_traverse__ method itself does it (i.e.
560
    the __bobo_traverse__ is the only element used for traversal lookup).
561
    Let's demonstrate:
562

563
      >>> from Products.Five.tests.testing import fancycontent
564
      >>> info = fancycontent.manage_addNonTraversableFancyContent(
565
      ...                                      folder, 'fancy_zope2', '')
566
      >>> folder.fancy_zope2.an_attribute = 'This is an attribute'
567
      >>> folder.fancy_zope2.unrestrictedTraverse(
568
      ...                             'an_attribute').index_html({})
569
      'an_attribute'
570

571
    Without a __bobo_traverse__ method this would have returned the attribute
572
    value 'This is an attribute'.  Let's make sure the same thing happens for
573
    an object that has been marked traversable:
574

575
      >>> folder.fancy.an_attribute = 'This is an attribute'
576
      >>> folder.fancy.unrestrictedTraverse(
577
      ...                             'an_attribute').index_html({})
578
      'an_attribute'
579

580
    If we traverse to something via an adapter lookup and it provides
581
    IAcquirer, it should get acquisition-wrapped so we can acquire
582
    attributes implicitly:
583

584
      >>> acquirer = folder.unrestrictedTraverse('acquirer')
585
      >>> acquirer.fancy
586
      <FancyContent ...>
587

588
    Clean up:
589

590
      >>> from zope.component.testing import tearDown
591
      >>> tearDown()
592

593
    Verify that after cleanup, there's no cruft left from five:traversable::
594

595
      >>> from Products.Five.browser.tests.test_traversable import SimpleClass
596
      >>> hasattr(SimpleClass, '__bobo_traverse__')
597
      False
598
      >>> hasattr(SimpleClass, '__fallback_traverse__')
599
      False
600

601
      >>> from Products.Five.tests.testing.fancycontent import FancyContent
602
      >>> hasattr(FancyContent, '__bobo_traverse__')
603
      True
604
      >>> hasattr(FancyContent.__bobo_traverse__, '__five_method__')
605
      False
606
      >>> hasattr(FancyContent, '__fallback_traverse__')
607
      False
608
    """
609

610

611
def test_view_doesnt_shadow_attribute():
1✔
612
    """
613
    Test that views don't shadow attributes, e.g. items in a folder.
614

615
    Let's first define a browser page for object managers called
616
    ``eagle``:
617

618
      >>> configure_zcml = '''
619
      ... <configure xmlns="http://namespaces.zope.org/zope"
620
      ...            xmlns:meta="http://namespaces.zope.org/meta"
621
      ...            xmlns:browser="http://namespaces.zope.org/browser"
622
      ...            xmlns:five="http://namespaces.zope.org/five">
623
      ...   <!-- make the zope2.Public permission work -->
624
      ...   <meta:redefinePermission from="zope2.Public" to="zope.Public" />
625
      ...   <browser:page
626
      ...       name="eagle"
627
      ...       for="OFS.interfaces.IObjectManager"
628
      ...       class="Products.Five.browser.tests.pages.SimpleView"
629
      ...       attribute="eagle"
630
      ...       permission="zope2.Public"
631
      ...       />
632
      ...   <browser:page
633
      ...       name="mouse"
634
      ...       for="OFS.interfaces.IObjectManager"
635
      ...       class="Products.Five.browser.tests.pages.SimpleView"
636
      ...       attribute="mouse"
637
      ...       permission="zope2.Public"
638
      ...       />
639
      ... </configure>'''
640
      >>> import Products.Five
641
      >>> from Zope2.App import zcml
642
      >>> zcml.load_config("configure.zcml", Products.Five)
643
      >>> zcml.load_string(configure_zcml)
644
      >>> folder = self.folder  # NOQA: F821
645

646
    Then we create a traversable folder...
647

648
      >>> from Products.Five.tests.testing import folder as ftf
649
      >>> ftf.manage_addFiveTraversableFolder(folder, 'ftf')
650

651
    and add an object called ``eagle`` to it:
652

653
      >>> from Products.Five.tests.testing import simplecontent
654
      >>> simplecontent.manage_addIndexSimpleContent(folder.ftf,
655
      ...                                            'eagle', 'Eagle')
656

657
    When we publish the ``ftf/eagle`` now, we expect the attribute to
658
    take precedence over the view during traversal:
659

660
      >>> folder.ftf.unrestrictedTraverse('eagle').index_html({})
661
      'Default index_html called'
662

663
    Of course, unless we explicitly want to lookup the view using @@:
664

665
      >>> folder.ftf.unrestrictedTraverse(
666
      ...     '@@eagle')() == 'The eagle has landed'
667
      True
668

669
    Some weird implementations of __bobo_traverse__, like the one
670
    found in OFS.Application, raise NotFound.  Five still knows how to
671
    deal with this, hence views work there too:
672

673
      >>> res = self.app.unrestrictedTraverse('@@eagle')()  # NOQA: F821
674
      >>> res == 'The eagle has landed'
675
      True
676

677
    However, acquired attributes *should* be shadowed. See discussion on
678
    http://codespeak.net/pipermail/z3-five/2006q2/001474.html
679

680
      >>> simplecontent.manage_addIndexSimpleContent(folder,
681
      ...                                            'mouse', 'Mouse')
682
      >>> folder.ftf.unrestrictedTraverse(
683
      ...     'mouse')() == 'The mouse has been eaten by the eagle'
684
      True
685

686
    Clean up:
687

688
      >>> from zope.component.testing import tearDown
689
      >>> tearDown()
690
    """
691

692

693
def test_suite():
1✔
694
    from Testing.ZopeTestCase import FunctionalDocTestSuite
1✔
695

696
    return unittest.TestSuite((
1✔
697
        unittest.defaultTestLoader.loadTestsFromTestCase(TestTraverse),
698
        FunctionalDocTestSuite(),
699
    ))
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc