zope.browsermenu-4.0.0/0000775000175000017500000000000011775031516014753 5ustar tseavertseaverzope.browsermenu-4.0.0/src/0000775000175000017500000000000011775031516015542 5ustar tseavertseaverzope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/0000775000175000017500000000000011775031516022560 5ustar tseavertseaverzope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/namespace_packages.txt0000664000175000017500000000000511775031516027106 0ustar tseavertseaverzope zope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/PKG-INFO0000664000175000017500000000373311775031516023663 0ustar tseavertseaverMetadata-Version: 1.0 Name: zope.browsermenu Version: 4.0.0 Summary: Browser menu implementation for Zope. Home-page: http://pypi.python.org/pypi/zope.browsermenu/ Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of browser menus and ZCML directives for configuring them. ======= CHANGES ======= 4.0.0 (2012-07-04) ================== - Strip noise from context actions in doctests. The output is now more meaningful, and hides irrelevant details. (forward-compatibility with ``zope.component`` 4.0.0). - Replaced deprecated ``zope.interface.implements`` usage with equivalent ``zope.interface.implementer`` decorator. - Dropped support for Python 2.4 and 2.5. 3.9.1 (2010-04-30) ================== - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/not-zip-safe0000664000175000017500000000000111775031422025002 0ustar tseavertseaver zope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/requires.txt0000664000175000017500000000033211775031516025156 0ustar tseavertseaversetuptools zope.browser zope.component>=3.7 zope.configuration zope.i18nmessageid zope.interface zope.pagetemplate>=3.5 zope.publisher zope.schema zope.security[untrustedpython] zope.traversing>3.7 [test] zope.testingzope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/SOURCES.txt0000664000175000017500000000213711775031516024447 0ustar tseavertseaverCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.browsermenu.egg-info/PKG-INFO src/zope.browsermenu.egg-info/SOURCES.txt src/zope.browsermenu.egg-info/dependency_links.txt src/zope.browsermenu.egg-info/namespace_packages.txt src/zope.browsermenu.egg-info/not-zip-safe src/zope.browsermenu.egg-info/requires.txt src/zope.browsermenu.egg-info/top_level.txt src/zope/browsermenu/README.txt src/zope/browsermenu/__init__.py src/zope/browsermenu/configure.zcml src/zope/browsermenu/field.py src/zope/browsermenu/interfaces.py src/zope/browsermenu/menu.py src/zope/browsermenu/meta.zcml src/zope/browsermenu/metaconfigure.py src/zope/browsermenu/metadirectives.py src/zope/browsermenu/tests/__init__.py src/zope/browsermenu/tests/addmenuitems.zcml src/zope/browsermenu/tests/menus-permissions.zcml src/zope/browsermenu/tests/menus.zcml src/zope/browsermenu/tests/test_addMenuItem.py src/zope/browsermenu/tests/test_directives.py src/zope/browsermenu/tests/test_fields.py src/zope/browsermenu/tests/test_menu.py src/zope/browsermenu/tests/test_menudirectives.pyzope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/top_level.txt0000664000175000017500000000000511775031516025305 0ustar tseavertseaverzope zope.browsermenu-4.0.0/src/zope.browsermenu.egg-info/dependency_links.txt0000664000175000017500000000000111775031516026626 0ustar tseavertseaver zope.browsermenu-4.0.0/src/zope/0000775000175000017500000000000011775031516016517 5ustar tseavertseaverzope.browsermenu-4.0.0/src/zope/__init__.py0000664000175000017500000000007011775031415020623 0ustar tseavertseaver__import__('pkg_resources').declare_namespace(__name__) zope.browsermenu-4.0.0/src/zope/browsermenu/0000775000175000017500000000000011775031516021067 5ustar tseavertseaverzope.browsermenu-4.0.0/src/zope/browsermenu/menu.py0000664000175000017500000001465511775031415022416 0ustar tseavertseaver############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Menu implementation code """ __docformat__ = "reStructuredText" import sys from zope.component import getAdapters, getUtility from zope.interface import Interface, implementer, providedBy from zope.interface.interfaces import IInterface from zope.pagetemplate.engine import Engine from zope.publisher.browser import BrowserView from zope.security import canAccess, checkPermission from zope.security.interfaces import Forbidden, Unauthorized from zope.security.proxy import removeSecurityProxy from zope.traversing.publicationtraverse import PublicationTraverser from zope.browsermenu.interfaces import IBrowserMenu, IMenuItemType from zope.browsermenu.interfaces import IBrowserMenuItem, IBrowserSubMenuItem from zope.browsermenu.interfaces import IMenuAccessView @implementer(IBrowserMenu) class BrowserMenu(object): """Browser Menu""" def __init__(self, id, title=u'', description=u''): self.id = id self.title = title self.description = description def getMenuItemType(self): return getUtility(IMenuItemType, self.id) def getMenuItems(self, object, request): """Return menu item entries in a TAL-friendly form.""" result = [] for name, item in getAdapters((object, request), self.getMenuItemType()): if item.available(): result.append(item) # Now order the result. This is not as easy as it seems. # # (1) Look at the interfaces and put the more specific menu entries # to the front. # (2) Sort unambigious entries by order and then by title. ifaces = list(providedBy(removeSecurityProxy(object)).__iro__) max_key = len(ifaces) def iface_index(item): iface = item._for if not iface: iface = Interface if IInterface.providedBy(iface): return ifaces.index(iface) if isinstance(removeSecurityProxy(object), item._for): # directly specified for class, this goes first. return -1 # no idea. This goes last. return max_key result = [(iface_index(item), item.order, item.title, item) for item in result] result.sort() result = [ {'title': title, 'description': item.description, 'action': item.action, 'selected': (item.selected() and u'selected') or u'', 'icon': item.icon, 'extra': item.extra, 'submenu': (IBrowserSubMenuItem.providedBy(item) and getMenu(item.submenuId, object, request)) or None} for index, order, title, item in result] return result @implementer(IBrowserMenuItem) class BrowserMenuItem(BrowserView): """Browser Menu Item Class""" title = u'' description = u'' action = u'' extra = None order = 0 permission = None filter = None icon = None _for = Interface def available(self): # Make sure we have the permission needed to access the menu's action if self.permission is not None: # If we have an explicit permission, check that we # can access it. if not checkPermission(self.permission, self.context): return False elif self.action != u'': # Otherwise, test access by attempting access path = self.action l = self.action.find('?') if l >= 0: path = self.action[:l] traverser = PublicationTraverser() try: view = traverser.traverseRelativeURL( self.request, self.context, path) except (Unauthorized, Forbidden, LookupError): return False else: # we're assuming that view pages are callable # this is a pretty sound assumption if not canAccess(view, '__call__'): return False # Make sure that we really want to see this menu item if self.filter is not None: try: include = self.filter(Engine.getContext( context = self.context, nothing = None, request = self.request, modules = sys.modules, )) except Unauthorized: return False else: if not include: return False return True def selected(self): request_url = self.request.getURL() normalized_action = self.action if self.action.startswith('@@'): normalized_action = self.action[2:] if request_url.endswith('/'+normalized_action): return True if request_url.endswith('/++view++'+normalized_action): return True if request_url.endswith('/@@'+normalized_action): return True return False @implementer(IBrowserSubMenuItem) class BrowserSubMenuItem(BrowserMenuItem): """Browser Menu Item Base Class""" submenuId = None def selected(self): if self.action is u'': return False return super(BrowserSubMenuItem, self).selected() def getMenu(id, object, request): """Return menu item entries in a TAL-friendly form.""" menu = getUtility(IBrowserMenu, id) return menu.getMenuItems(object, request) def getFirstMenuItem(id, object, request): """Get the first item of a menu.""" items = getMenu(id, object, request) if items: return items[0] return None @implementer(IMenuAccessView) class MenuAccessView(BrowserView): """A view allowing easy access to menus.""" def __getitem__(self, menuId): return getMenu(menuId, self.context, self.request) zope.browsermenu-4.0.0/src/zope/browsermenu/README.txt0000664000175000017500000004650511775031415022575 0ustar tseavertseaver============= 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', , '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', (, ), , 'Save'), ('adapter', (, ), , '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 () must implement IBrowserMenuItem >>> items.subMenuItem(context, SaveOptions, 'Failed', item_class=object) Traceback (most recent call last): ... ValueError: Item class () must implement IBrowserSubMenuItem zope.browsermenu-4.0.0/src/zope/browsermenu/interfaces.py0000664000175000017500000001244711775031415023572 0ustar tseavertseaver############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Menu-specific interfaces """ from zope.i18nmessageid import ZopeMessageFactory as _ from zope.interface import Interface, directlyProvides from zope.interface.interfaces import IInterface from zope.schema import TextLine, Text, URI, Int class IMenuItemType(IInterface): """Menu item type Menu item types are interfaces that define classes of menu items. """ class AddMenu(Interface): """Special menu for providing a list of addable objects.""" directlyProvides(AddMenu, IMenuItemType) class IBrowserMenu(Interface): """Menu Menus are objects that can return a list of menu items they contain. How they generate this list is up to them. Commonly, however, they will look up adapters that provide the ``IBrowserMenuItem`` interface. """ id = TextLine( title=_("Menu Id"), description=_("The id uniquely identifies this menu."), required=True ) title = TextLine( title=_("Menu title"), description=_("The title provides the basic label for the menu."), required=False ) description = Text( title=_("Menu description"), description=_("A description of the menu. This might be shown " "on menu pages or in pop-up help for menus."), required=False ) def getMenuItems(object, request): """Return a TAL-friendly list of menu items. The object (acts like the context) and request can be used to select the items that are available. """ class IBrowserMenuItem(Interface): """Menu type An interface that defines a menu. """ title = TextLine( title=_("Menu item title"), description=_("The title provides the basic label for the menu item."), required=True ) description = Text( title=_("Menu item description"), description=_("A description of the menu item. This might be shown " "on menu pages or in pop-up help for menu items."), required=False ) action = TextLine( title=_("The URL to display if the item is selected"), description=_("When a user selects a browser menu item, the URL" "given in the action is displayed. The action is " "usually given as a relative URL, relative to the " "object the menu item is for."), required=True ) order = Int( title=_("Menu item ordering hint"), description=_("This attribute provides a hint for menu item ordering." "Menu items will generally be sorted by the `for_`" "attribute and then by the order.") ) filter_string = TextLine( title=_("A condition for displaying the menu item"), description=_("The condition is given as a TALES expression. The " "expression has access to the variables:\n" "\n" "context -- The object the menu is being displayed " "for\n" "\n" "request -- The browser request\n" "\n" "nothing -- None\n" "\n" "The menu item will not be displayed if there is a \n" "filter and the filter evaluates to a false value."), required=False) icon = URI( title=_("Icon URI"), description=_("URI of the icon representing this menu item")) def available(): """Test whether the menu item should be displayed A menu item might not be available for an object, for example due to security limitations or constraints. """ class IBrowserSubMenuItem(IBrowserMenuItem): """A menu item that points to a sub-menu.""" submenuId = TextLine( title=_("Sub-Menu Id"), description=_("The menu id of the menu that describes the " "sub-menu below this item."), required=True) action = TextLine( title=_("The URL to display if the item is selected"), description=_("When a user selects a browser menu item, the URL " "given in the action is displayed. The action is " "usually given as a relative URL, relative to the " "object the menu item is for."), required=False ) class IMenuAccessView(Interface): """View that provides access to menus""" def __getitem__(menu_id): """Get menu information Return a sequence of dictionaries with labels and actions, where actions are relative URLs. """ zope.browsermenu-4.0.0/src/zope/browsermenu/meta.zcml0000664000175000017500000000233211775031415022702 0ustar tseavertseaver zope.browsermenu-4.0.0/src/zope/browsermenu/metadirectives.py0000664000175000017500000001616511775031415024460 0ustar tseavertseaver############################################################################# # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Menu ZCML directives """ from zope.interface import Interface from zope.configuration.fields import GlobalObject, GlobalInterface from zope.configuration.fields import Tokens, Path, PythonIdentifier, MessageID from zope.schema import TextLine, Id, Int, Bool from zope.security.zcml import Permission from zope.component.zcml import IBasicViewInformation from zope.browsermenu.field import MenuField class IMenuDirective(Interface): """Define a browser menu""" id = TextLine( title=u"The name of the menu.", description=u"This is, effectively, an id.", required=False ) title = MessageID( title=u"Title", description=u"A descriptive title for documentation purposes", required=False ) description = MessageID( title=u"Description", description=u"A description title of the menu.", required=False ) class_ = GlobalObject( title=u"Menu Class", description=u"The menu class used to generate the menu.", required=False ) interface = GlobalInterface( title=u"The menu's interface.", required=False ) class IMenuItemsDirective(Interface): """ Define a group of browser menu items This directive is useful when many menu items are defined for the same interface and menu. """ menu = MenuField( title=u"Menu name", description=u"The (name of the) menu the items are defined for", required=True, ) for_ = GlobalObject( title=u"Interface", description=u"The interface the menu items are defined for", required=True ) layer = GlobalInterface( title=u"Layer", description=u"The Layer for which the item is declared.", required=False ) permission = Permission( title=u"The permission needed access the item", description=u""" This can usually be inferred by the system, however, doing so may be expensive. When displaying a menu, the system tries to traverse to the URLs given in each action to determine whether the url is accessible to the current user. This can be avoided if the permission is given explicitly.""", required=False ) class IMenuItem(Interface): """Common menu item configuration """ title = MessageID( title=u"Title", description=u"The text to be displayed for the menu item", required=True ) description = MessageID( title=u"A longer explanation of the menu item", description=u""" A UI may display this with the item or display it when the user requests more assistance.""", required=False ) icon = TextLine( title=u"Icon Path", description=u"Path to the icon resource representing this menu item.", required=False ) permission = Permission( title=u"The permission needed access the item", description=u""" This can usually be inferred by the system, however, doing so may be expensive. When displaying a menu, the system tries to traverse to the URLs given in each action to determine whether the url is accessible to the current user. This can be avoided if the permission is given explicitly.""", required=False ) filter = TextLine( title=u"A condition for displaying the menu item", description=u""" The condition is given as a TALES expression. The expression has access to the variables: context -- The object the menu is being displayed for request -- The browser request nothing -- None The menu item will not be displayed if there is a filter and the filter evaluates to a false value.""", required=False ) order = Int( title=u"Order", description=u"A relative position of the menu item in the menu.", required=False, default=0 ) item_class = GlobalObject( title=u"Menu item class", description=u""" A class to be used as a factory for creating menu item""", required=False ) class IMenuItemSubdirective(IMenuItem): """Define a menu item within a group of menu items""" action = TextLine( title=u"The relative url to use if the item is selected", description=u""" The url is relative to the object the menu is being displayed for.""", required=True ) class IMenuItemDirective(IMenuItemsDirective, IMenuItemSubdirective): """Define one menu item""" class ISubMenuItemSubdirective(IMenuItem): """Define a menu item that represents a a sub menu. For a sub-menu menu item, the action is optional, this the item itself might not represent a destination, but just an entry point to the sub menu. """ action = TextLine( title=u"The relative url to use if the item is selected", description=u""" The url is relative to the object the menu is being displayed for.""", required=False ) submenu = TextLine( title=u"Sub-Menu Id", description=u"The menu that will be used to provide the sub-entries.", required=True, ) class ISubMenuItemDirective(IMenuItemsDirective, ISubMenuItemSubdirective): """Define one menu item""" class IAddMenuItemDirective(IMenuItem): """Define an add-menu item""" for_ = GlobalInterface( title=u"Interface", description=u"The interface the menu items are defined for", required=False ) class_ = GlobalObject( title=u"Class", description=u""" A class to be used as a factory for creating new objects""", required=False ) factory = Id( title=u"Factory", description=u"A factory id for creating new objects", required = False, ) view = TextLine( title=u"Custom view name", description=u"The name of a custom add view", required = False, ) menu = MenuField( title=u"Menu name", description=u"The (name of the) menu the items are defined for", required=False, ) layer = GlobalInterface( title=u"The layer the custom view is declared for", description=u"The default layer for which the custom view is " u"applicable. By default it is applied to all layers.", required=False ) zope.browsermenu-4.0.0/src/zope/browsermenu/tests/0000775000175000017500000000000011775031516022231 5ustar tseavertseaverzope.browsermenu-4.0.0/src/zope/browsermenu/tests/menus-permissions.zcml0000664000175000017500000000071011775031414026613 0ustar tseavertseaver zope.browsermenu-4.0.0/src/zope/browsermenu/tests/test_addMenuItem.py0000664000175000017500000002320011775031414026030 0ustar tseavertseaver############################################################################# # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the addMenuItem directive >>> context = Context() >>> addMenuItem(context, class_=X, title="Add an X", ... permission="zope.ManageContent") >>> context [('utility', , 'BrowserAdd__zope.browsermenu.tests.test_addMenuItem.X'), (, ), ('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ import unittest from doctest import DocTestSuite import re import pprint import cStringIO from zope.interface import Interface from zope.publisher.interfaces.browser import IBrowserRequest from zope.component.interface import provideInterface from zope.browsermenu.metaconfigure import addMenuItem from zope.browsermenu.metaconfigure import _checkViewFor atre = re.compile(' at [0-9a-fA-Fx]+') class IX(Interface): pass class X(object): pass class ILayerStub(IBrowserRequest): pass class MenuStub(object): pass class Context(object): info = '' def __init__(self): self.actions = [] def action(self, discriminator, callable, args=(), kw={}, order=0): if discriminator is None: if callable is provideInterface: self.actions.append((callable, args[1])) #name is args[0] elif callable is _checkViewFor: self.actions.append((callable, args[2])) else: self.actions.append(discriminator) def __repr__(self): stream = cStringIO.StringIO() pprinter = pprint.PrettyPrinter(stream=stream, width=60) pprinter.pprint(self.actions) r = stream.getvalue() return (''.join(atre.split(r))).strip() def test_w_factory(): """ >>> context = Context() >>> addMenuItem(context, factory="x.y.z", title="Add an X", ... permission="zope.ManageContent", description="blah blah", ... filter="context/foo") >>> context [('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ def test_w_factory_and_view(): """ >>> context = Context() >>> addMenuItem(context, factory="x.y.z", title="Add an X", ... permission="zope.ManageContent", description="blah blah", ... filter="context/foo", view="AddX") >>> context [(, 'AddX'), ('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ def test_w_factory_class_view(): """ >>> context = Context() >>> addMenuItem(context, class_=X, title="Add an X", ... permission="zope.ManageContent", description="blah blah", ... filter="context/foo", view="AddX") >>> import pprint >>> context [('utility', , 'BrowserAdd__zope.browsermenu.tests.test_addMenuItem.X'), (, ), (, 'AddX'), ('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ def test_w_for_factory(): """ >>> context = Context() >>> addMenuItem(context, for_=IX, factory="x.y.z", title="Add an X", ... permission="zope.ManageContent", description="blah blah", ... filter="context/foo") >>> context [(, ), ('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ def test_w_factory_layer(): """ >>> context = Context() >>> addMenuItem(context, factory="x.y.z", title="Add an X", layer=ILayerStub, ... permission="zope.ManageContent", description="blah blah", ... filter="context/foo") >>> context [('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ def test_w_for_menu_factory(): """ >>> context = Context() >>> addMenuItem(context, for_=IX, menu=MenuStub, ... factory="x.y.z", title="Add an X", ... permission="zope.ManageContent", description="blah blah", ... filter="context/foo") >>> context [(, ), ('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ def test_w_factory_icon_extra_order(): """ >>> context = Context() >>> addMenuItem(context, factory="x.y.z", title="Add an X", ... permission="zope.ManageContent", description="blah blah", ... filter="context/foo", icon=u'/@@/icon.png', extra='Extra', ... order=99) >>> context [('adapter', (, ), , 'Add an X'), (, ), (, ), (, )] """ from zope.configuration.xmlconfig import XMLConfig import zope.browsermenu import zope.component from zope.testing import cleanup class TestAddMenuItem(cleanup.CleanUp, unittest.TestCase): def setUp(self): super(TestAddMenuItem, self).setUp() XMLConfig('meta.zcml', zope.component)() XMLConfig('meta.zcml', zope.browsermenu)() def test_addMenuItemDirectives(self): XMLConfig('tests/addmenuitems.zcml', zope.browsermenu)() def test_suite(): return unittest.TestSuite(( DocTestSuite(), unittest.makeSuite(TestAddMenuItem), )) if __name__ == '__main__': unittest.main() zope.browsermenu-4.0.0/src/zope/browsermenu/tests/menus.zcml0000664000175000017500000000342011775031414024243 0ustar tseavertseaver zope.browsermenu-4.0.0/src/zope/browsermenu/tests/test_menudirectives.py0000664000175000017500000001134011775031414026664 0ustar tseavertseaver############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Browser Menu Directives Tests """ import unittest from zope.configuration.xmlconfig import XMLConfig from zope.interface import Interface, implementer from zope.publisher.browser import TestRequest from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.browsermenu.interfaces import IBrowserMenu from zope.security.interfaces import Unauthorized, Forbidden import zope.component import zope.security from zope.testing import cleanup import zope.browsermenu template = """ %s """ class I1(Interface): pass class I11(I1): pass class I12(I1): pass class I111(I11): pass @implementer(I1) class C1(object): pass class I2(Interface): pass @implementer(I2) class C2(object): pass @implementer(IBrowserPublisher, I111) class TestObject(object): def f(self): pass def browserDefault(self, r): return self, () def publishTraverse(self, request, name): if name[:1] == 'f': raise Forbidden(name) if name[:1] == 'u': raise Unauthorized(name) return self.f class IMyLayer(Interface): pass class IMySkin(IMyLayer, IDefaultBrowserLayer): pass class TestPermissions(cleanup.CleanUp, unittest.TestCase): def setUp(self): super(TestPermissions, self).setUp() XMLConfig('meta.zcml', zope.browsermenu)() XMLConfig('meta.zcml', zope.security)() def testMenuItemsPermission(self): XMLConfig('tests/menus-permissions.zcml', zope.browsermenu)() menu = zope.component.getUtility(IBrowserMenu, 'test_id') # This is a bit icky, but the menu hides too much stuff from us. items = zope.component.getAdapters((C2(), TestRequest()), menu.getMenuItemType()) item = list(items)[0][1] self.assertEquals("zope.View", item.permission) class Test(cleanup.CleanUp, unittest.TestCase): def setUp(self): super(Test, self).setUp() XMLConfig('meta.zcml', zope.browsermenu)() def testMenusAndMenuItems(self): XMLConfig('tests/menus.zcml', zope.browsermenu)() menu = zope.browsermenu.menu.getMenu( 'test_id', TestObject(), TestRequest()) def d(n): return {'action': "a%s" % n, 'title': "t%s" % n, 'description': u'', 'selected': '', 'submenu': None, 'icon': None, 'extra': None} self.assertEqual(menu[:-1], [d(5), d(6), d(3), d(2), d(1)]) self.assertEqual( menu[-1], {'submenu': [{'submenu': None, 'description': u'', 'extra': None, 'selected': u'', 'action': u'a10', 'title': u't10', 'icon': None}], 'description': u'', 'extra': None, 'selected': u'', 'action': u'', 'title': u's1', 'icon': None}) first = zope.browsermenu.menu.getFirstMenuItem( 'test_id', TestObject(), TestRequest()) self.assertEqual(first, d(5)) def testMenuItemWithLayer(self): XMLConfig('tests/menus.zcml', zope.browsermenu)() menu = zope.browsermenu.menu.getMenu( 'test_id', TestObject(), TestRequest()) self.assertEqual(len(menu), 6) menu = zope.browsermenu.menu.getMenu( 'test_id', TestObject(), TestRequest(skin=IMyLayer)) self.assertEqual(len(menu), 2) menu = zope.browsermenu.menu.getMenu( 'test_id', TestObject(), TestRequest(skin=IMySkin)) self.assertEqual(len(menu), 8) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(Test), unittest.makeSuite(TestPermissions), )) if __name__=='__main__': unittest.main(defaultTest='test_suite') zope.browsermenu-4.0.0/src/zope/browsermenu/tests/test_directives.py0000664000175000017500000001270611775031414026006 0ustar tseavertseaver############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """'browser' namespace directive tests """ import sys import os import unittest from cStringIO import StringIO from doctest import DocTestSuite from zope import component from zope.interface import Interface, implementer, directlyProvides, providedBy import zope.security.management from zope.configuration.xmlconfig import xmlconfig, XMLConfig from zope.configuration.exceptions import ConfigurationError from zope.publisher.browser import TestRequest from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserSkinType from zope.security.proxy import removeSecurityProxy, ProxyFactory from zope.security.permission import Permission from zope.security.interfaces import IPermission from zope.traversing.adapters import DefaultTraversable from zope.traversing.interfaces import ITraversable import zope.browsermenu from zope.component.testfiles.views import IC, V1, VZMI, R1, IV from zope.browsermenu.menu import getFirstMenuItem, BrowserMenu from zope.browsermenu.interfaces import IMenuItemType, IBrowserMenu from zope.testing import cleanup tests_path = os.path.join( os.path.dirname(zope.browsermenu.__file__), 'tests') template = """ %s """ request = TestRequest() class M1(BrowserMenu): pass class V2(V1, object): def action(self): return self.action2() def action2(self): return "done" class VT(V1, object): def publishTraverse(self, request, name): try: return int(name) except: return super(VT, self).publishTraverse(request, name) @implementer(IC) class Ob(object): pass ob = Ob() class NCV(object): "non callable view" def __init__(self, context, request): pass class CV(NCV): "callable view" def __call__(self): pass @implementer(Interface) class C_w_implements(NCV): def index(self): return self class ITestMenu(Interface): """Test menu.""" directlyProvides(ITestMenu, IMenuItemType) class ITestLayer(IBrowserRequest): """Test Layer.""" class ITestSkin(ITestLayer): """Test Skin.""" class MyResource(object): def __init__(self, request): self.request = request class Test(cleanup.CleanUp, unittest.TestCase): def setUp(self): super(Test, self).setUp() XMLConfig('meta.zcml', zope.browsermenu)() component.provideAdapter(DefaultTraversable, (None,), ITraversable) def tearDown(self): if 'test_menu' in dir(sys.modules['zope.app.menus']): delattr(sys.modules['zope.app.menus'], 'test_menu') super(Test, self).tearDown() def testMenuOverride(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ( ''' ''' ))) menu1 = component.getUtility(IBrowserMenu, 'test_menu') menuItem1 = getFirstMenuItem('test_menu', ob, TestRequest()) xmlconfig(StringIO(template % ( ''' ''' ))) menu2 = component.getUtility(IBrowserMenu, 'test_menu') menuItem2 = getFirstMenuItem('test_menu', ob, TestRequest()) self.assertNotEqual(menu1, menu2) self.assertEqual(menuItem1, menuItem2) def testMenuItemNeedsFor(self): # directive fails if no 'for' argument was provided from zope.configuration.exceptions import ConfigurationError self.assertRaises(ConfigurationError, xmlconfig, StringIO(template % ''' ''' )) # it works, when the argument is there and a valid interface xmlconfig(StringIO(template % ''' ''' )) def test_suite(): return unittest.makeSuite(Test) zope.browsermenu-4.0.0/src/zope/browsermenu/tests/test_menu.py0000664000175000017500000000214111775031414024601 0ustar tseavertseaver############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Browser Menu Item Tests """ import unittest import doctest import pprint from zope.testing import cleanup def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('../README.txt', setUp=lambda test:cleanup.setUp(), tearDown=lambda test:cleanup.tearDown(), globs={'pprint': pprint.pprint}, optionflags=doctest.NORMALIZE_WHITESPACE), )) zope.browsermenu-4.0.0/src/zope/browsermenu/tests/__init__.py0000664000175000017500000000000011775031414024325 0ustar tseavertseaverzope.browsermenu-4.0.0/src/zope/browsermenu/tests/addmenuitems.zcml0000664000175000017500000000124511775031414025576 0ustar tseavertseaver zope.browsermenu-4.0.0/src/zope/browsermenu/tests/test_fields.py0000664000175000017500000000167411775031414025115 0ustar tseavertseaver############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test fields. """ import unittest import doctest from zope.testing import cleanup def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite('zope.browsermenu.field', setUp=lambda test:cleanup.setUp(), tearDown=lambda test:cleanup.tearDown()), )) zope.browsermenu-4.0.0/src/zope/browsermenu/metaconfigure.py0000664000175000017500000002611111775031415024270 0ustar tseavertseaver############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Menu Directives Configuration Handlers """ from zope.browser.interfaces import IAdding from zope.component import getGlobalSiteManager, getUtility from zope.component.interface import provideInterface from zope.component.zcml import adapter, proxify, utility from zope.configuration.exceptions import ConfigurationError from zope.interface import Interface from zope.interface.interface import InterfaceClass from zope.pagetemplate.engine import Engine from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.security.checker import InterfaceChecker, CheckerPublic from zope.security.metaconfigure import ClassDirective from zope.browsermenu.menu import BrowserMenu, BrowserMenuItem, BrowserSubMenuItem from zope.browsermenu.interfaces import IBrowserMenu, IMenuItemType from zope.browsermenu.interfaces import IBrowserMenuItem, IBrowserSubMenuItem from zope.browsermenu.interfaces import AddMenu # Create special modules that contain all menu item types from types import ModuleType as module import sys try: import zope.app except ImportError: # we doesn't always have zope.app now sys.modules['zope.app'] = module('app') menus = module('menus') sys.modules['zope.app.menus'] = menus _order_counter = {} def menuDirective(_context, id=None, class_=BrowserMenu, interface=None, title=u'', description=u''): """Registers a new browser menu.""" if id is None and interface is None: raise ConfigurationError( "You must specify the 'id' or 'interface' attribute.") if interface is None: if id in dir(menus): # reuse existing interfaces for the id, without this we are not # able to override menus. interface = getattr(menus, id) else: interface = InterfaceClass(id, (), __doc__='Menu Item Type: %s' %id, __module__='zope.app.menus') # Add the menu item type to the `menus` module. # Note: We have to do this immediately, so that directives using the # MenuField can find the menu item type. setattr(menus, id, interface) path = 'zope.app.menus.' + id else: path = interface.__module__ + '.' + interface.getName() # If an id was specified, make this menu available under this id. # Note that the menu will be still available under its path, since it # is an adapter, and the `MenuField` can resolve paths as well. if id is None: id = path else: # Make the interface available in the `zope.app.menus` module, so # that other directives can find the interface under the name # before the CA is setup. _context.action( discriminator = ('browser', 'MenuItemType', path), callable = provideInterface, args = (path, interface, IMenuItemType, _context.info) ) setattr(menus, id, interface) # Register the layer interface as an interface _context.action( discriminator = ('interface', path), callable = provideInterface, args = (path, interface), kw = {'info': _context.info} ) # Register the menu item type interface as an IMenuItemType _context.action( discriminator = ('browser', 'MenuItemType', id), callable = provideInterface, args = (id, interface, IMenuItemType, _context.info) ) # Register the menu as a utility utility(_context, IBrowserMenu, class_(id, title, description), name=id) def menuItemDirective(_context, menu, for_, action, title, description=u'', icon=None, filter=None, permission=None, layer=IDefaultBrowserLayer, extra=None, order=0, item_class=None): """Register a single menu item.""" return menuItemsDirective(_context, menu, for_, layer).menuItem( _context, action, title, description, icon, filter, permission, extra, order, item_class) def subMenuItemDirective(_context, menu, for_, title, submenu, action=u'', description=u'', icon=None, filter=None, permission=None, layer=IDefaultBrowserLayer, extra=None, order=0, item_class=None): """Register a single sub-menu menu item.""" return menuItemsDirective(_context, menu, for_, layer).subMenuItem( _context, submenu, title, description, action, icon, filter, permission, extra, order, item_class) class MenuItemFactory(object): """generic factory for menu items.""" def __init__(self, factory, **kwargs): self.factory = factory if 'permission' in kwargs and kwargs['permission'] == 'zope.Public': kwargs['permission'] = CheckerPublic self.kwargs = kwargs def __call__(self, context, request): item = self.factory(context, request) for key, value in self.kwargs.items(): setattr(item, key, value) if item.permission is not None: checker = InterfaceChecker(IBrowserMenuItem, item.permission) item = proxify(item, checker) return item class menuItemsDirective(object): """Register several menu items for a particular menu.""" menuItemClass = BrowserMenuItem subMenuItemClass = BrowserSubMenuItem def __init__(self, _context, menu, for_, layer=IDefaultBrowserLayer, permission=None): self.for_ = for_ self.menuItemType = menu self.layer = layer self.permission = permission def menuItem(self, _context, action, title, description=u'', icon=None, filter=None, permission=None, extra=None, order=0, item_class=None): if filter is not None: filter = Engine.compile(filter) if permission is None: permission = self.permission if order == 0: order = _order_counter.get(self.for_, 1) _order_counter[self.for_] = order + 1 if item_class is None: item_class = self.menuItemClass if not IBrowserMenuItem.implementedBy(item_class): raise ValueError("Item class (%s) must implement IBrowserMenuItem" % item_class) factory = MenuItemFactory( item_class, title=title, description=description, icon=icon, action=action, filter=filter, permission=permission, extra=extra, order=order, _for=self.for_) adapter(_context, (factory,), self.menuItemType, (self.for_, self.layer), name=title) def subMenuItem(self, _context, submenu, title, description=u'', action=u'', icon=None, filter=None, permission=None, extra=None, order=0, item_class=None): if filter is not None: filter = Engine.compile(filter) if permission is None: permission = self.permission if order == 0: order = _order_counter.get(self.for_, 1) _order_counter[self.for_] = order + 1 if item_class is None: item_class = self.subMenuItemClass if not IBrowserSubMenuItem.implementedBy(item_class): raise ValueError("Item class (%s) must implement IBrowserSubMenuItem" % item_class) factory = MenuItemFactory( item_class, title=title, description=description, icon=icon, action=action, filter=filter, permission=permission, extra=extra, order=order, _for=self.for_, submenuId=submenu) adapter(_context, (factory,), self.menuItemType, (self.for_, self.layer), name=title) def __call__(self, _context): # Nothing to do. pass def _checkViewFor(for_=None, layer=None, view_name=None): """Check if there is a view of that name registered for IAdding and IBrowserRequest. If not raise a ConfigurationError It will raise a ConfigurationError if : o view="" o if view_name is not registred """ if view_name is None: raise ConfigurationError( "Within a addMenuItem directive the view attribut" " is optional but can\'t be empty" ) gsm = getGlobalSiteManager() if gsm.adapters.lookup((for_, layer), Interface, view_name) is None: raise ConfigurationError( "view name %s not found " %view_name ) def addMenuItem(_context, title, description='', menu=None, for_=None, class_=None, factory=None, view=None, icon=None, filter=None, permission=None, layer=IDefaultBrowserLayer, extra=None, order=0, item_class=None): """Create an add menu item for a given class or factory As a convenience, a class can be provided, in which case, a factory is automatically defined based on the class. In this case, the factory id is based on the class name. """ if for_ is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', for_) ) forname = 'For' + for_.getName() else: for_ = IAdding forname = '' if menu is not None: if isinstance(menu, (str, unicode)): menu = getUtility(IMenuItemType, menu) if menu is None: raise ValueError("Missing menu id '%s'" % menu) if class_ is None: if factory is None: raise ValueError("Must specify either class or factory") else: if factory is not None: raise ValueError("Can't specify both class and factory") if permission is None: raise ValueError( "A permission must be specified when a class is used") factory = "BrowserAdd%s__%s.%s" % ( forname, class_.__module__, class_.__name__) ClassDirective(_context, class_).factory(_context, id=factory) extra = {'factory': factory} if view: action = view # This action will check if the view exists _context.action( discriminator = None, callable = _checkViewFor, args = (for_, layer, view), order=999999 ) else: action = factory if menu == None: menu = AddMenu return menuItemsDirective(_context, menu, for_, layer=layer).menuItem( _context, action, title, description, icon, filter, permission, extra, order, item_class) zope.browsermenu-4.0.0/src/zope/browsermenu/configure.zcml0000664000175000017500000000070111775031415023733 0ustar tseavertseaver zope.browsermenu-4.0.0/src/zope/browsermenu/__init__.py0000664000175000017500000000000011775031415023164 0ustar tseavertseaverzope.browsermenu-4.0.0/src/zope/browsermenu/field.py0000664000175000017500000000674311775031415022534 0ustar tseavertseaver############################################################################# # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Menu field """ __docformat__ = 'restructuredtext' from zope.component import queryUtility from zope.component.interfaces import ComponentLookupError from zope.configuration.exceptions import ConfigurationError from zope.configuration.fields import GlobalObject from zope.schema import ValidationError from zope.browsermenu.interfaces import IMenuItemType class MenuField(GlobalObject): r"""This fields represents a menu (item type). Besides being able to look up the menu by importing it, we also try to look up the name in the site manager. >>> from zope.interface import directlyProvides >>> from zope.interface.interface import InterfaceClass >>> menu1 = InterfaceClass('menu1', (), ... __doc__='Menu Item Type: menu1', ... __module__='zope.app.menus') >>> directlyProvides(menu1, IMenuItemType) >>> menus = None >>> class Resolver(object): ... def resolve(self, path): ... if path.startswith('zope.app.menus') and \ ... hasattr(menus, 'menu1') or \ ... path == 'zope.browsermenu.menus.menu1': ... return menu1 ... raise ConfigurationError('menu1') >>> field = MenuField() >>> field = field.bind(Resolver()) Test 1: Import the menu ----------------------- >>> field.fromUnicode('zope.browsermenu.menus.menu1') is menu1 True Test 2: We have a shortcut name. Import the menu from `zope.app.menus1`. ------------------------------------------------------------------------ >>> from types import ModuleType as module >>> import sys >>> menus = module('menus') >>> old = sys.modules.get('zope.app.menus', None) >>> sys.modules['zope.app.menus'] = menus >>> setattr(menus, 'menu1', menu1) >>> field.fromUnicode('menu1') is menu1 True >>> if old is not None: ... sys.modules['zope.app.menus'] = old Test 3: Get the menu from the Site Manager ------------------------------------------ >>> from zope.component import provideUtility >>> provideUtility(menu1, IMenuItemType, 'menu1') >>> field.fromUnicode('menu1') is menu1 True """ def fromUnicode(self, u): name = str(u.strip()) try: value = queryUtility(IMenuItemType, name) except ComponentLookupError: # The component architecture is not up and running. pass else: if value is not None: self.validate(value) return value try: value = self.context.resolve('zope.app.menus.'+name) except ConfigurationError, v: try: value = self.context.resolve(name) except ConfigurationError, v: raise ValidationError(v) self.validate(value) return value zope.browsermenu-4.0.0/README.txt0000664000175000017500000000051311775031415016446 0ustar tseavertseaver======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of browser menus and ZCML directives for configuring them. zope.browsermenu-4.0.0/setup.cfg0000664000175000017500000000007311775031516016574 0ustar tseavertseaver[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope.browsermenu-4.0.0/COPYRIGHT.txt0000664000175000017500000000004011775031415017054 0ustar tseavertseaverZope Foundation and Contributorszope.browsermenu-4.0.0/PKG-INFO0000664000175000017500000000373311775031516016056 0ustar tseavertseaverMetadata-Version: 1.0 Name: zope.browsermenu Version: 4.0.0 Summary: Browser menu implementation for Zope. Home-page: http://pypi.python.org/pypi/zope.browsermenu/ Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of browser menus and ZCML directives for configuring them. ======= CHANGES ======= 4.0.0 (2012-07-04) ================== - Strip noise from context actions in doctests. The output is now more meaningful, and hides irrelevant details. (forward-compatibility with ``zope.component`` 4.0.0). - Replaced deprecated ``zope.interface.implements`` usage with equivalent ``zope.interface.implementer`` decorator. - Dropped support for Python 2.4 and 2.5. 3.9.1 (2010-04-30) ================== - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope.browsermenu-4.0.0/bootstrap.py0000664000175000017500000000733011775031415017343 0ustar tseavertseaver############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope.browsermenu-4.0.0/LICENSE.txt0000664000175000017500000000402611775031415016576 0ustar tseavertseaverZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zope.browsermenu-4.0.0/setup.py0000664000175000017500000000472011775031415016466 0ustar tseavertseaver############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zope.browsermenu setup """ from setuptools import setup, find_packages long_description = (open('README.txt').read() + '\n\n' + open('CHANGES.txt').read()) setup(name='zope.browsermenu', version = '4.0.0', url='http://pypi.python.org/pypi/zope.browsermenu/', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', classifiers = ['Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3', ], description='Browser menu implementation for Zope.', long_description=long_description, license='ZPL 2.1', packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], include_package_data=True, install_requires=['setuptools', 'zope.browser', 'zope.component>=3.7', 'zope.configuration', 'zope.i18nmessageid', 'zope.interface', 'zope.pagetemplate>=3.5', 'zope.publisher', 'zope.schema', 'zope.security[untrustedpython]', 'zope.traversing>3.7', ], extras_require={ 'test': ['zope.testing'], }, zip_safe = False, ) zope.browsermenu-4.0.0/buildout.cfg0000664000175000017500000000071711775031415017266 0ustar tseavertseaver[buildout] develop = . parts = test coverage-test coverage-report pydev [test] recipe = zc.recipe.testrunner eggs = zope.browsermenu [test] [coverage-test] recipe = zc.recipe.testrunner eggs = zope.browsermenu [test] defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') [pydev] recipe = pb.recipes.pydev eggs = zope.browsermenu zope.browsermenu-4.0.0/CHANGES.txt0000664000175000017500000000115711775031451016566 0ustar tseavertseaver======= CHANGES ======= 4.0.0 (2012-07-04) ================== - Strip noise from context actions in doctests. The output is now more meaningful, and hides irrelevant details. (forward-compatibility with ``zope.component`` 4.0.0). - Replaced deprecated ``zope.interface.implements`` usage with equivalent ``zope.interface.implementer`` decorator. - Dropped support for Python 2.4 and 2.5. 3.9.1 (2010-04-30) ================== - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher.