This file is indexed.

/usr/share/pyshared/zope/browsermenu/README.txt is in python-zope.browsermenu 4.0.0-0ubuntu2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
=============
Browser Menus
=============

Browser menus are used to categorize browser actions, such as the views of a
content component or the addable components of a container. In essence they
provide the same functionality as menu bars in desktop application.

  >>> from zope.browsermenu import menu, metaconfigure

Menus are simple components that have an id, title and description. They also
must provide a method called ``getMenuItems(object, request)`` that returns a
TAL-friendly list of information dictionaries. We will see this in detail
later. The default menu implementation, however, makes the menu be very
transparent by identifying the menu through an interface. So let's define and
register a simple edit menu:

  >>> import zope.interface
  >>> class EditMenu(zope.interface.Interface):
  ...     """This is an edit menu."""

  >>> from zope.browsermenu.interfaces import IMenuItemType
  >>> zope.interface.directlyProvides(EditMenu, IMenuItemType)

  >>> from zope.component import provideUtility
  >>> provideUtility(EditMenu, IMenuItemType, 'edit')

Now we have to create and register the menu itself:

  >>> from zope.browsermenu.interfaces import IBrowserMenu
  >>> provideUtility(
  ...     menu.BrowserMenu('edit', u'Edit', u'Edit Menu'), IBrowserMenu, 'edit')

Note that these steps seem like a lot of boilerplate, but all this work is
commonly done for you via ZCML. An item in a menu is simply an adapter that
provides. In the following section we will have a closer look at the browser
menu item:

``BrowserMenuItem`` class
-------------------------

The browser menu item represents an entry in the menu. Essentially, the menu
item is a browser view of a content component. Thus we have to create a
content component first:

  >>> class IContent(zope.interface.Interface):
  ...     pass

  >>> from zope.publisher.interfaces.browser import IBrowserPublisher
  >>> from zope.security.interfaces import Unauthorized, Forbidden

  >>> @zope.interface.implementer(IContent, IBrowserPublisher)
  ... class Content(object):
  ...
  ...     def foo(self):
  ...         pass
  ...
  ...     def browserDefault(self, r):
  ...         return self, ()
  ...
  ...     def publishTraverse(self, request, name):
  ...         if name.startswith('fb'):
  ...             raise Forbidden, name
  ...         if name.startswith('ua'):
  ...             raise Unauthorized, name
  ...         if name.startswith('le'):
  ...             raise LookupError, name
  ...         return self.foo

We also implemented the ``IBrowserPublisher`` interface, because we want to
make the object traversable, so that we can make availability checks later.

Since the ``BrowserMenuItem`` is just a view, we can initiate it with an
object and a request.

  >>> from zope.publisher.browser import TestRequest
  >>> item = menu.BrowserMenuItem(Content(), TestRequest())

Note that the menu item knows *nothing* about the menu itself. It purely
depends on the adapter registration to determine in which menu it will
appear. The advantage is that a menu item can be reused in several menus.

Now we add a title, description, order and icon and see whether we can then
access the value. Note that these assignments are always automatically done by
the framework.

  >>> item.title = u'Item 1'
  >>> item.title
  u'Item 1'

  >>> item.description = u'This is Item 1.'
  >>> item.description
  u'This is Item 1.'

  >>> item.order
  0
  >>> item.order = 1
  >>> item.order
  1

  >>> item.icon is None
  True
  >>> item.icon = u'/@@/icon.png'
  >>> item.icon
  u'/@@/icon.png'

Since there is no permission or view specified yet, the menu item should
be available and not selected.

  >>> item.available()
  True
  >>> item.selected()
  False

There are two ways to deny availability of a menu item: (1) the current
user does not have the correct permission to access the action or the menu
item itself, or (2) the filter returns ``False``, in which case the menu
item should also not be shown. 

  >>> from zope.security.interfaces import IPermission
  >>> from zope.security.permission import Permission
  >>> perm = Permission('perm', 'Permission')
  >>> provideUtility(perm, IPermission, 'perm')

  >>> class ParticipationStub(object):
  ...     principal = 'principal'
  ...     interaction = None


In the first case, the permission of the menu item was explicitely
specified. Make sure that the user needs this permission to make the menu
item available.

  >>> item.permission = perm

Now, we are not setting any user. This means that the menu item should be
available.

  >>> from zope.security.management import newInteraction, endInteraction
  >>> endInteraction()
  >>> newInteraction()
  >>> item.available()
  True

Now we specify a principal that does not have the specified permission.

  >>> endInteraction()
  >>> newInteraction(ParticipationStub())
  >>> item.available()
  False

In the second case, the permission is not explicitely defined and the
availability is determined by the permission required to access the
action.

  >>> item.permission = None

  All views starting with 'fb' are forbidden, the ones with 'ua' are
  unauthorized and all others are allowed.

  >>> item.action = u'fb'
  >>> item.available()
  False
  >>> item.action = u'ua'
  >>> item.available()
  False
  >>> item.action = u'a'
  >>> item.available()
  True

Also, sometimes a menu item might be registered for a view that does not
exist. In those cases the traversal mechanism raises a `TraversalError`, which
is a special type of `LookupError`. All actions starting with `le` should
raise this error:

  >>> item.action = u'le'
  >>> item.available()
  False

Now let's test filtering. If the filter is specified, it is assumed to be
a TALES obejct.

  >>> from zope.pagetemplate.engine import Engine
  >>> item.action = u'a'
  >>> item.filter = Engine.compile('not:context')
  >>> item.available()
  False
  >>> item.filter = Engine.compile('context')
  >>> item.available()
  True

Finally, make sure that the menu item can be selected.

  >>> item.request = TestRequest(SERVER_URL='http://127.0.0.1/@@view.html',
  ...                            PATH_INFO='/@@view.html')

  >>> item.selected()
  False
  >>> item.action = u'view.html'
  >>> item.selected()
  True
  >>> item.action = u'@@view.html'
  >>> item.selected()
  True
  >>> item.request = TestRequest(
  ...     SERVER_URL='http://127.0.0.1/++view++view.html',
  ...     PATH_INFO='/++view++view.html')
  >>> item.selected()
  True
  >>> item.action = u'otherview.html'
  >>> item.selected()
  False


``BrowserSubMenuItem`` class
----------------------------

The menu framework also allows for submenus. Submenus can be inserted by
creating a special menu item that simply points to another menu to be
inserted:

  >>> item = menu.BrowserSubMenuItem(Content(), TestRequest())

The framework will always set the sub-menu automatically (we do it
manually here):

  >>> class SaveOptions(zope.interface.Interface):
  ...     "A sub-menu that describes available save options for the content."

  >>> zope.interface.directlyProvides(SaveOptions, IMenuItemType)

  >>> provideUtility(SaveOptions, IMenuItemType, 'save')
  >>> provideUtility(menu.BrowserMenu('save', u'Save', u'Save Menu'),
  ...                IBrowserMenu, 'save')

Now we can assign the sub-menu id to the menu item: 

  >>> item.submenuId = 'save'

Also, the ``action`` attribute for the browser sub-menu item is optional,
because you often do not want the item itself to represent something. The rest
of the class is identical to the ``BrowserMenuItem`` class.


Getting a Menu
--------------

Now that we know how the single menu item works, let's have a look at how menu
items get put together to a menu. But let's first create some menu items and
register them as adapters with the component architecture.

Register the edit menu entries first. We use the menu item factory to create
the items:

  >>> from zope.component import provideAdapter
  >>> from zope.publisher.interfaces.browser import IBrowserRequest

  >>> undo = metaconfigure.MenuItemFactory(menu.BrowserMenuItem, title="Undo", 
  ...                                 action="undo.html")
  >>> provideAdapter(undo, (IContent, IBrowserRequest), EditMenu, 'undo')

  >>> redo = metaconfigure.MenuItemFactory(menu.BrowserMenuItem, title="Redo",
  ...                                 action="redo.html", icon="/@@/redo.png")
  >>> provideAdapter(redo, (IContent, IBrowserRequest), EditMenu, 'redo')

  >>> save = metaconfigure.MenuItemFactory(menu.BrowserSubMenuItem, title="Save", 
  ...                                 submenuId='save', order=2)
  >>> provideAdapter(save, (IContent, IBrowserRequest), EditMenu, 'save')

And now the save options:

  >>> saveas = metaconfigure.MenuItemFactory(menu.BrowserMenuItem, title="Save as", 
  ...                                   action="saveas.html")
  >>> provideAdapter(saveas, (IContent, IBrowserRequest), 
  ...                SaveOptions, 'saveas')

  >>> saveall = metaconfigure.MenuItemFactory(menu.BrowserMenuItem, title="Save all",
  ...                                    action="saveall.html")
  >>> provideAdapter(saveall, (IContent, IBrowserRequest), 
  ...                SaveOptions, 'saveall')

Note that we can also register menu items for classes:


  >>> new = metaconfigure.MenuItemFactory(menu.BrowserMenuItem, title="New",
  ...                                 action="new.html", _for=Content)
  >>> provideAdapter(new, (Content, IBrowserRequest), EditMenu, 'new')


The utility that is used to generate the menu into a TAL-friendly
data-structure is ``getMenu()``::

  getMenu(menuId, object, request)

where ``menuId`` is the id originally specified for the menu. Let's look up the
menu now:

  >>> pprint(menu.getMenu('edit', Content(), TestRequest()))
  [{'action': 'new.html',
    'description': u'',
    'extra': None,
    'icon': None,
    'selected': u'',
    'submenu': None,
    'title': 'New'},
  {'action': 'redo.html',
    'description': u'',
    'extra': None,
    'icon': '/@@/redo.png',
    'selected': u'',
    'submenu': None,
    'title': 'Redo'},
   {'action': 'undo.html',
    'description': u'',
    'extra': None,
    'icon': None,
    'selected': u'',
    'submenu': None,
    'title': 'Undo'},
   {'action': u'',
    'description': u'',
    'extra': None,
    'icon': None,
    'selected': u'',
    'submenu': [{'action': 'saveall.html',
                 'description': u'',
                 'extra': None,
                 'icon': None,
                 'selected': u'',
                 'submenu': None,
                 'title': 'Save all'},
                {'action': 'saveas.html',
                 'description': u'',
                 'extra': None,
                 'icon': None,
                 'selected': u'',
                 'submenu': None,
                 'title': 'Save as'}],
    'title': 'Save'}]


Custom ``IBrowserMenu`` Implementations
---------------------------------------

Until now we have only seen how to use the default menu implementation. Much
of the above boilerplate was necessary just to support custom menus. But what
could custom menus do? Sometimes menu items are dynamically generated based on
a certain state of the object the menu is for. For example, you might want to
show all items in a folder-like component. So first let's create this
folder-like component:

  >>> class Folderish(Content):
  ...     names = ['README.txt', 'logo.png', 'script.py']

Now we create a menu using the names to create a menu:

  >>> from zope.browsermenu.interfaces import IBrowserMenu

  >>> @zope.interface.implementer(IBrowserMenu)
  ... class Items(object):
  ...  
  ...     def __init__(self, id, title=u'', description=u''):
  ...         self.id = id
  ...         self.title = title
  ...         self.description = description
  ...     
  ...     def getMenuItems(self, object, request):
  ...         return [{'title': name,
  ...                  'description': None,
  ...                  'action': name + '/manage',
  ...                  'selected': u'',
  ...                  'icon': None,
  ...                  'extra': {},
  ...                  'submenu': None}
  ...                 for name in object.names]

and register it:

  >>> provideUtility(Items('items', u'Items', u'Items Menu'),
  ...                IBrowserMenu, 'items')

We can now get the menu items using the previously introduced API:

  >>> pprint(menu.getMenu('items', Folderish(), TestRequest()))
  [{'action': 'README.txt/manage',
    'description': None,
    'extra': {},
    'icon': None,
    'selected': u'',
    'submenu': None,
    'title': 'README.txt'},
   {'action': 'logo.png/manage',
    'description': None,
    'extra': {},
    'icon': None,
    'selected': u'',
    'submenu': None,
    'title': 'logo.png'},
   {'action': 'script.py/manage',
    'description': None,
    'extra': {},
    'icon': None,
    'selected': u'',
    'submenu': None,
    'title': 'script.py'}]


``MenuItemFactory`` class
-------------------------

As you have seen above already, we have used the menu item factory to generate
adapter factories for menu items. The factory needs a particular
``IBrowserMenuItem`` class to instantiate. Here is an example using a dummy
menu item class:
  
  >>> class DummyBrowserMenuItem(object):
  ...     "a dummy factory for menu items"
  ...     def __init__(self, context, request):
  ...         self.context = context
  ...         self.request = request
  
To instantiate this class, pass the factory and the other arguments as keyword
arguments (every key in the arguments should map to an attribute of the menu
item class). We use dummy values for this example.
  
  >>> factory = metaconfigure.MenuItemFactory(
  ...     DummyBrowserMenuItem, title='Title', description='Description', 
  ...     icon='Icon', action='Action', filter='Filter', 
  ...     permission='zope.Public', extra='Extra', order='Order', _for='For')
  >>> factory.factory is DummyBrowserMenuItem
  True
  
The "zope.Public" permission needs to be translated to ``CheckerPublic``.
  
  >>> from zope.security.checker import CheckerPublic
  >>> factory.kwargs['permission'] is CheckerPublic
  True
  
Call the factory with context and request to return the instance.  We continue
to use dummy values.
  
  >>> item = factory('Context', 'Request')
  
The returned value should be an instance of the ``DummyBrowserMenuItem``, and
have all of the values we initially set on the factory.
  
  >>> isinstance(item, DummyBrowserMenuItem)
  True
  >>> item.context
  'Context'
  >>> item.request
  'Request'
  >>> item.title
  'Title'
  >>> item.description
  'Description'
  >>> item.icon
  'Icon'
  >>> item.action
  'Action'
  >>> item.filter
  'Filter'
  >>> item.permission is CheckerPublic
  True
  >>> item.extra
  'Extra'
  >>> item.order
  'Order'
  >>> item._for
  'For'
  
If you pass a permission other than ``zope.Public`` to the
``MenuItemFactory``, it should pass through unmodified.
  
  >>> factory = metaconfigure.MenuItemFactory(
  ...     DummyBrowserMenuItem, title='Title', description='Description', 
  ...     icon='Icon', action='Action', filter='Filter', 
  ...     permission='another.Permission', extra='Extra', order='Order', 
  ...     _for='For_')
  >>> factory.kwargs['permission']
  'another.Permission'


Directive Handlers
------------------

``menu`` Directive Handler
~~~~~~~~~~~~~~~~~~~~~~~~~~

Provides a new menu (item type).

  >>> class Context(object):
  ...     info = u'doc'
  ...     def __init__(self): 
  ...         self.actions = []
  ...
  ...     def action(self, **kw): 
  ...         self.actions.append(kw)

Possibility 1: The Old Way
++++++++++++++++++++++++++
  
  >>> context = Context()
  >>> metaconfigure.menuDirective(context, u'menu1', title=u'Menu 1')
  >>> iface = context.actions[0]['args'][1]
  >>> iface.getName()
  u'menu1'

  >>> import sys
  >>> hasattr(sys.modules['zope.app.menus'], 'menu1')
  True

  >>> del sys.modules['zope.app.menus'].menu1

Possibility 2: Just specify an interface
++++++++++++++++++++++++++++++++++++++++

  >>> class menu1(zope.interface.Interface):
  ...     pass

  >>> context = Context()
  >>> metaconfigure.menuDirective(context, interface=menu1)
  >>> context.actions[0]['args'][1] is menu1
  True

Possibility 3: Specify an interface and an id
+++++++++++++++++++++++++++++++++++++++++++++

  >>> context = Context()
  >>> metaconfigure.menuDirective(context, id='menu1', interface=menu1)

  >>> pprint([action['discriminator'] for action in context.actions])
  [('browser', 'MenuItemType', '__builtin__.menu1'),
   ('interface', '__builtin__.menu1'),
   ('browser', 'MenuItemType', 'menu1'),
   ('utility',
    <InterfaceClass zope.browsermenu.interfaces.IBrowserMenu>,
    'menu1'),
   None]
   
Here are some disallowed configurations.

  >>> context = Context()
  >>> metaconfigure.menuDirective(context)
  Traceback (most recent call last):
  ...
  ConfigurationError: You must specify the 'id' or 'interface' attribute.

  >>> metaconfigure.menuDirective(context, title='Menu 1')
  Traceback (most recent call last):
  ...
  ConfigurationError: You must specify the 'id' or 'interface' attribute.


``menuItems`` Directive Handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Register several menu items for a particular menu.

  >>> class TestMenuItemType(zope.interface.Interface):
  ...     pass

  >>> class ITest(zope.interface.Interface): 
  ...     pass

  >>> context = Context()
  >>> items = metaconfigure.menuItemsDirective(context, TestMenuItemType, ITest)
  >>> context.actions
  []
  >>> items.menuItem(context, u'view.html', 'View')
  >>> items.subMenuItem(context, SaveOptions, 'Save')

  >>> disc = [action['discriminator'] for action in context.actions]
  >>> disc.sort()
  >>> pprint(disc[-2:])
  [('adapter',
    (<InterfaceClass __builtin__.ITest>,
     <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
    <InterfaceClass __builtin__.TestMenuItemType>,
    'Save'),
   ('adapter',
    (<InterfaceClass __builtin__.ITest>,
     <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
    <InterfaceClass __builtin__.TestMenuItemType>,
    'View')]

Custom menu item classes
~~~~~~~~~~~~~~~~~~~~~~~~

We can register menu items and sub menu items with custom classes instead
of ones used by default. For that, we need to create an implementation
of IBrowserMenuItem or IBrowserSubMenuItem.

  >>> context = Context()
  >>> items = metaconfigure.menuItemsDirective(context, TestMenuItemType, ITest)
  >>> context.actions
  []

Let's create a custom menu item class that inherits standard BrowserMenuItem:

  >>> class MyMenuItem(menu.BrowserMenuItem):
  ...    pass

  >>> items.menuItem(context, u'view.html', 'View', item_class=MyMenuItem)

Also create a custom sub menu item class inheriting standard BrowserSubMenuItem:

  >>> class MySubMenuItem(menu.BrowserSubMenuItem):
  ...    pass

  >>> items.subMenuItem(context, SaveOptions, 'Save', item_class=MySubMenuItem)

  >>> actions = sorted(context.actions, key=lambda a:a['discriminator'])
  >>> factories = [action['args'][1] for action in actions][-2:]

  >>> factories[0].factory is MySubMenuItem
  True

  >>> factories[1].factory is MyMenuItem
  True

These directive will fail if you provide an item_class that does not
implement IBrowserMenuItem/IBrowserSubMenuItem:

  >>> items.menuItem(context, u'fail', 'Failed', item_class=object)
  Traceback (most recent call last):
  ...
  ValueError: Item class (<type 'object'>) must implement IBrowserMenuItem

  >>> items.subMenuItem(context, SaveOptions, 'Failed', item_class=object)
  Traceback (most recent call last):
  ...
  ValueError: Item class (<type 'object'>) must implement IBrowserSubMenuItem