z3c.menu.ready2go


Namez3c.menu.ready2go JSON
Version 2.0 PyPI version JSON
download
home_pagehttps://github.com/zopefoundation/z3c.menu.ready2go
SummaryA ready to go menu for Zope3
upload_time2023-02-07 10:10:03
maintainer
docs_urlNone
authorStephan Richter, Roger Ineichen and the Zope Community
requires_python>=3.7
licenseZPL 2.1
keywords zope3 z3c ready 2 go menu
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            This package provides a "ready-to-go" menu implementation based on viewlets for 
Zope 3.


.. contents::

===============
Ready 2 go Menu
===============

The z3c.menu.ready2go package provides a menu implementation which allows you
to implement menus based on content providers and viewlets.

First let's setup our defualt menu item template:

  >>> import os
  >>> import zope.component
  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
  >>> from zope.publisher.interfaces.browser import IBrowserView
  >>> from z3c.template.interfaces import IContentTemplate
  >>> from z3c.template.template import TemplateFactory
  >>> import z3c.menu.ready2go
  >>> baseDir = os.path.split(z3c.menu.ready2go.__file__)[0]
  >>> itemTemplate = os.path.join(baseDir, 'item.pt')
  >>> itemTemplateFactory = TemplateFactory(itemTemplate, 'text/html')
  >>> zope.component.provideAdapter(itemTemplateFactory,
  ...     (IBrowserView, IDefaultBrowserLayer), IContentTemplate)


Global Menu
-----------

Let's create some menu and register them as viewlet manager:

  >>> from zope.viewlet.interfaces import IViewlet
  >>> from zope.viewlet import manager
  >>> from z3c.menu.ready2go import interfaces
  >>> from z3c.menu.ready2go import IGlobalMenu
  >>> from z3c.menu.ready2go import ISiteMenu
  >>> from z3c.menu.ready2go import IContextMenu
  >>> from z3c.menu.ready2go import IAddMenu
  >>> from z3c.menu.ready2go.manager import MenuManager

And we configure our menu item as viewlet Managers. This is normaly done by the
``viewletManager`` ZCML directive:

  >>> GlobalMenu = manager.ViewletManager('left', IGlobalMenu,
  ...     bases=(MenuManager,))

  >>> SiteMenu = manager.ViewletManager('left', ISiteMenu,
  ...     bases=(MenuManager,))

  >>> ContextMenu = manager.ViewletManager('left', IContextMenu,
  ...     bases=(MenuManager,))

  >>> AddMenu = manager.ViewletManager('left', IAddMenu,
  ...     bases=(MenuManager,))

Our menu managers implement IMenuManager:

  >>> interfaces.IMenuManager.implementedBy(GlobalMenu)
  True

  >>> interfaces.IMenuManager.implementedBy(SiteMenu)
  True

  >>> interfaces.IMenuManager.implementedBy(ContextMenu)
  True

  >>> interfaces.IMenuManager.implementedBy(AddMenu)
  True

We also need our checker adapter which can check if a menu item is available
and/or selected:

  >>> from z3c.menu.ready2go import checker
  >>> zope.component.provideAdapter(checker.GlobalSelectedChecker)
  >>> zope.component.provideAdapter(checker.SiteSelectedChecker)
  >>> zope.component.provideAdapter(checker.ContextSelectedChecker)

Now we have to define a site and a context:

  >>> import zope.interface
  >>> from zope.container import contained, btree
  >>> from zope.location.interfaces import IContained
  >>> from zope.component.interfaces import IPossibleSite
  >>> from zope.site.site import SiteManagerContainer
  >>> from zope.site.site import LocalSiteManager

  >>> @zope.interface.implementer(IPossibleSite)
  ... class Site(btree.BTreeContainer, SiteManagerContainer):
  ...     def __init__(self):
  ...         super(Site, self).__init__()
  ...         self.setSiteManager(LocalSiteManager(self))

  >>> @zope.interface.implementer(IContained)
  ... class Content(contained.Contained):
  ...     pass

  >>> root['site'] = Site()
  >>> site = root['site']

Now we have to set the site object as site. This is normaly done by the
traverser but we do this here with the hooks helper because we do not really
traaverse to the site within the publisher/traverser:

  >>> from zope.component import hooks
  >>> hooks.setSite(site)

  >>> site['content'] = Content()
  >>> content = site['content']

  >>> from zope.publisher.browser import TestRequest
  >>> request = TestRequest()

And we need a view which knows about it's parent:

  >>> @zope.interface.implementer(IBrowserView)
  ... class View(contained.Contained):
  ...
  ...     def __init__(self, context, request):
  ...         self.__parent__ = context
  ...         self.context = context
  ...         self.request = request

  >>> view = View(content, request)

Our menus can adapt the context, request and view. See IViewletManager in
zope.viewlet for more infos about this pattern. If we render them, there is an
empty string returned. This means the menus don't find menu items for rendering:

  >>> globalMenu = GlobalMenu(content, request, view)
  >>> globalMenu.update()
  >>> globalMenu.render()
  ''

  >>> siteMenu = SiteMenu(content, request, view)
  >>> siteMenu.update()
  >>> siteMenu.render()
  ''

  >>> contextMenu = ContextMenu(content, request, view)
  >>> contextMenu.update()
  >>> contextMenu.render()
  ''

  >>> addMenu = AddMenu(content, request, view)
  >>> addMenu.update()
  >>> addMenu.render()
  ''


Global Menu Item
----------------

Now we register a context menu item for our IGlobalMenu:

  >>> from z3c.menu.ready2go.item import GlobalMenuItem
  >>> class MyGlobalMenuItem(GlobalMenuItem):
  ...
  ...     viewName = 'root.html'

Now we need a security checker for our menu item

  >>> from zope.security.checker import NamesChecker, defineChecker
  >>> viewletChecker = NamesChecker(('update', 'render'))
  >>> defineChecker(MyGlobalMenuItem, viewletChecker)

And we configure our menu item for IGlobalMenu. This is normaly done by the
``viewlet`` ZCML directive:

  >>> zope.component.provideAdapter(
  ...     MyGlobalMenuItem,
  ...     (zope.interface.Interface, IDefaultBrowserLayer,
  ...     IBrowserView, IGlobalMenu),
  ...     IViewlet, name='My Global')

Now let's update the menu manager and see that this manager now contains
the menu item:

  >>> globalMenu.update()
  >>> myGlobalMenuItem = globalMenu.viewlets[0]
  >>> myGlobalMenuItem
  <MyGlobalMenuItem 'My Global'>

Now let's render the global menu manager and you can see that the menu item
get rendered:

  >>> print(globalMenu.render())
  <li>
    <a href="http://127.0.0.1/root.html"><span>My Global</span></a>
  </li>


Site Menu Item
--------------

Now we register a context menu item for our ISiteMenu:

  >>> import zope.component
  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer

  >>> from z3c.menu.ready2go.item import SiteMenuItem
  >>> class MySiteMenuItem(SiteMenuItem):
  ...
  ...     viewName = 'site.html'

Now we need a security checker for our menu item

  >>> from zope.security.checker import NamesChecker, defineChecker
  >>> viewletChecker = NamesChecker(('update', 'render'))
  >>> defineChecker(MySiteMenuItem, viewletChecker)

And we configure our menu item for ISiteMenu. This is normaly done by the
``viewlet`` ZCML directive:

  >>> zope.component.provideAdapter(
  ...     MySiteMenuItem,
  ...     (zope.interface.Interface, IDefaultBrowserLayer,
  ...     IBrowserView, ISiteMenu),
  ...     IViewlet, name='My Site')

Now let's render the site menu again. You can see that we ve got a menu item
and the url points to our site:

  >>> siteMenu.update()
  >>> print(siteMenu.render())
  <li>
    <a href="http://127.0.0.1/site/site.html"><span>My Site</span></a>
  </li>


Context Menu Item
-----------------

Now we register a context menu item for our IContextMenu:

  >>> import zope.component
  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer

  >>> from z3c.menu.ready2go.item import ContextMenuItem
  >>> class MyContextMenuItem(ContextMenuItem):
  ...     viewName = 'context.html'
  ...     weight = 1

Now we need a security checker for our menu item

  >>> from zope.security.checker import NamesChecker, defineChecker
  >>> viewletChecker = NamesChecker(('update', 'render'))
  >>> defineChecker(MyContextMenuItem, viewletChecker)

And we configure our menu item for IContextMenu. This is normaly done by the
``viewlet`` ZCML directive:

  >>> zope.component.provideAdapter(
  ...     MyContextMenuItem,
  ...     (zope.interface.Interface, IDefaultBrowserLayer,
  ...     IBrowserView, IContextMenu),
  ...     IViewlet, name='My Context')

Now let's render the context menu again. You can see that we ve got a menu
item. Another important point here is, that the url of such ContextMemuItem
implementations point to the context of the view:

  >>> contextMenu.update()
  >>> print(contextMenu.render())
  <li>
    <a href="http://127.0.0.1/site/content/context.html"><span>My Context</span></a>
  </li>

Let's set the view  __name__ to ``context.html``. This will reflect that
the view offers the same name that our context menu needs to get rendered as
selected:

  >>> view.__name__ = 'context.html'

Now try again and see if the context menu item get rendered as selected:

  >>> contextMenu.update()
  >>> print(contextMenu.render())
  <li class="selected">
    <a href="http://127.0.0.1/site/content/context.html"><span>My Context</span></a>
  </li>

Also, let's check that menu item is marked selected even if we provided a viewName in
the ``@@context.html`` form:

  >>> MyContextMenuItem.viewName = '@@context.html'
  >>> contextMenu.update()
  >>> print(contextMenu.render())
  <li class="selected">
    <a href="http://127.0.0.1/site/content/@@context.html"><span>My Context</span></a>
  </li>

Okay, change viewName back to ``context.html`` for further tests:

  >>> MyContextMenuItem.viewName = 'context.html'

Now add a second context menu item and check if we can use the cssInActive
argument which is normaly a empty string:

  >>> class InActiveMenuItem(ContextMenuItem):
  ...     viewName = 'inActive.html'
  ...     cssInActive = 'inActive'
  ...     weight = 2

  >>> defineChecker(InActiveMenuItem, viewletChecker)

  >>> zope.component.provideAdapter(
  ...     InActiveMenuItem,
  ...     (zope.interface.Interface, IDefaultBrowserLayer,
  ...     IBrowserView, IContextMenu),
  ...     IViewlet, name='In Active')

Now update and render again:

  >>> contextMenu.update()
  >>> print(contextMenu.render())
  <li class="selected">
    <a href="http://127.0.0.1/site/content/context.html"><span>My Context</span></a>
  </li>
  <li class="inActive">
    <a href="http://127.0.0.1/site/content/inActive.html"><span>In Active</span></a>
  </li>

AddMenu
-------

The add menu can be used for offering links to any kind of add forms per
context. This allows us to offer independent add form links doesn't matter which
form framework is used. Let's now define such a simple AddMenuItem pointing
to a add form url. Not; the add form and it's url do not exist in thsi test.
This aslo means there is no guarantee that a form exist if a add menu item
is configured.

  >>> from z3c.menu.ready2go.item import AddMenuItem
  >>> class MyAddMenuItem(AddMenuItem):
  ...
  ...     viewName = 'addSomething.html'

Now we need a security checker for our menu item

  >>> from zope.security.checker import NamesChecker, defineChecker
  >>> viewletChecker = NamesChecker(('update', 'render'))
  >>> defineChecker(MyAddMenuItem, viewletChecker)

And we configure our menu item for IAddMenu. This is normaly done by the
``viewlet`` ZCML directive:

  >>> zope.component.provideAdapter(
  ...     MyAddMenuItem,
  ...     (zope.interface.Interface, IDefaultBrowserLayer,
  ...     IBrowserView, IAddMenu),
  ...     IViewlet, name='My AddMenu')

Now we can update and render our add menu:

  >>> addMenu.update()
  >>> print(addMenu.render())
  <li>
    <a href="http://127.0.0.1/site/content/addSomething.html"><span>My AddMenu</span></a>
  </li>


Menu groups
-----------

The global and the site menu items are grouped menu items. This means such menu
items should get rendered as selected if a context menu item is selected. This
reflects the menu hierarchie. Let's show how we can solve this not so simple
problem. We offer a ISelectedChecker adapter which can decide if a menu get
rendered as selected or not. This is very usefull because normaly a menu get
registered and later we add views and can not change the menu item
implementation. Let's see how such an adapter can handle an existing menu,
context and view setup and change the selected rendering. We register a
selected checker for our site menu item:

  >>> zope.component.provideAdapter(checker.TrueSelectedChecker,
  ...     (IContained, IDefaultBrowserLayer, None, ISiteMenu, MySiteMenuItem),
  ...     interfaces.ISelectedChecker)

Now we can render the site menu again. Note that our context is still the
sample content object.

  >>> siteMenu.update()
  >>> print(siteMenu.render())
  <li class="selected">
    <a href="http://127.0.0.1/site/site.html"><span>My Site</span></a>
  </li>

This reflects that the site menu is a group menu which the context menu item
of the content object is selected too.

  >>> contextMenu.update()
  >>> print(contextMenu.render())
  <li class="selected">
    <a href="http://127.0.0.1/site/content/context.html"><span>My Context</span></a>
  </li>
  <li class="inActive">
    <a href="http://127.0.0.1/site/content/inActive.html"><span>In Active</span></a>
  </li>


EmptyMenuManager
----------------

There is a empty menu manager whihc could be used for override existing
menu managers.

  >>> from z3c.menu.ready2go.manager import EmptyMenuManager
  >>> emptyMenu = EmptyMenuManager(None, None, None)

Our empty menu manager implements ``IMenuManager``:

  >>> interfaces.IMenuManager.providedBy(emptyMenu)
  True

This empty menu manager returns allways an empty string if we render them:

  >>> emptyMenu.update()
  >>> emptyMenu.render()
  ''


Special use case
----------------

We have some special use case because of Zope's internals. One important part
is that our menu heavy depend on context and it's __parent__ chain to the
zope application root. This is not allways supported by Zopes default setup.
One part is the bad integrated application control part which fakes a root
object which doesn't know about the real childs of the real root from the
ZODB e.g. application root. Now we will show you that our menu by default
render no items if we get such a fake root which messes up our menu structure.

Let's define a object which does not know about any __parent__.

  >>> nirvana = Content()
  >>> nirvanaView = View(nirvana, request)

Now we can check what's happen to the menus if we adapt the parent less nirvana
context and update and render the menus. You can see that the global menu does
not contain any menu item. That's because the global menu items tries to find
the root by traversing from the context to the root by the __parent__ chain
and we don't support any parent for our nirvana object:

  >>> globalMenu = GlobalMenu(nirvana, request, nirvanaView)
  >>> globalMenu.update()
  >>> globalMenu.render()
  ''

Also the SiteMenu doesn't contain any menu item because of the parent less
object:

  >>> siteMenu = SiteMenu(nirvana, request, nirvanaView)
  >>> siteMenu.update()
  >>> siteMenu.render()
  ''

  >>> contextMenu = ContextMenu(nirvana, request, nirvanaView)
  >>> contextMenu.update()
  >>> contextMenu.render()
  ''

  >>> addMenu = AddMenu(nirvana, request, nirvanaView)
  >>> addMenu.update()
  >>> addMenu.render()
  ''


===================
Z3C Menu directives
===================

Show how we can use the menu directive. Register the meta configuration for 
the directive.

First let's setup our defualt menu item template first:

  >>> import os
  >>> import zope.component
  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
  >>> from zope.publisher.interfaces.browser import IBrowserView
  >>> from z3c.template.interfaces import IContentTemplate
  >>> from z3c.template.template import TemplateFactory
  >>> import z3c.menu.ready2go
  >>> baseDir = os.path.split(z3c.menu.ready2go.__file__)[0]
  >>> itemTemplate = os.path.join(baseDir, 'item.pt')
  >>> itemTemplateFactory = TemplateFactory(itemTemplate, 'text/html')
  >>> zope.component.provideAdapter(itemTemplateFactory,
  ...     (IBrowserView, IDefaultBrowserLayer), IContentTemplate)

  >>> import sys
  >>> from zope.configuration import xmlconfig
  >>> import z3c.menu.ready2go
  >>> context = xmlconfig.file('meta.zcml', z3c.menu.ready2go)

We need to register our checker adapter which can check if a menu item is 
selected or not:

  >>> import zope.component
  >>> from z3c.menu.ready2go import checker
  >>> zope.component.provideAdapter(checker.ContextSelectedChecker)

Let's define a content object:

  >>> from z3c.menu.ready2go import testing
  >>> sampleContent = testing.Sample('Sample Content')

Now add the content object to our site root:

  >>> root['sample'] = sampleContent

Now we can define our test menu manager:

  >>> from zope.viewlet.manager import ViewletManager
  >>> from z3c.menu.ready2go import manager
  >>> FirstMenu = ViewletManager('left', testing.IFirstMenu,
  ...     bases=(manager.MenuManager,))

  >>> SecondMenu = ViewletManager('left', testing.ISecondMenu,
  ...     bases=(manager.MenuManager,))

And we need a view which knows about it's parent:

  >>> from zope.publisher.browser import TestRequest
  >>> request = TestRequest()
  >>> firstView = testing.FirstView(sampleContent, request)
  >>> testing.IFirstView.providedBy(firstView)
  True

  >>> secondView = testing.SecondView(sampleContent, request)
  >>> testing.ISecondView.providedBy(secondView)
  True

As you can see the menu is not selected if we access the page:

  >>> firstMenu = FirstMenu(sampleContent, request, firstView)
  >>> testing.IFirstMenu.providedBy(firstMenu)
  True

  >>> firstMenu.update()
  >>> firstMenu.render()
  ''

  >>> secondMenu = SecondMenu(sampleContent, request, secondView)
  >>> testing.ISecondMenu.providedBy(secondMenu)
  True

  >>> secondMenu.update()
  >>> secondMenu.render()
  ''

Now we need some menu items for the first menu:

  >>> from zope.publisher.interfaces.browser import IBrowserView
  >>> from zope.publisher.interfaces.browser import IBrowserRequest
  >>> from zope.viewlet.interfaces import IViewlet
  >>> zope.component.provideAdapter(
  ...     testing.FirstMenuItem,
  ...     (zope.interface.Interface, IBrowserRequest,
  ...     IBrowserView, testing.IFirstMenu),
  ...     IViewlet, name='First Menu')

  >>> zope.component.provideAdapter(
  ...     testing.SecondMenuItem,
  ...     (zope.interface.Interface, IBrowserRequest,
  ...     IBrowserView, testing.IFirstMenu),
  ...     IViewlet, name='Second Menu')

And we need some menu items for the second menu:

  >>> zope.component.provideAdapter(
  ...     testing.FirstMenuItem,
  ...     (zope.interface.Interface, IBrowserRequest,
  ...     IBrowserView, testing.ISecondMenu),
  ...     IViewlet, name='First Menu')

  >>> zope.component.provideAdapter(
  ...     testing.SecondMenuItem,
  ...     (zope.interface.Interface, IBrowserRequest,
  ...     IBrowserView, testing.ISecondMenu),
  ...     IViewlet, name='Second Menu')

Now render the menu manager again and you can see that we've got some menu
items. but you can see that this menu items are not selected:

  >>> firstMenu = FirstMenu(sampleContent, request, firstView)
  >>> firstMenu.update()
  >>> print(firstMenu.render())
  <li>
    <a><span>First Menu</span></a>
  </li>
  <li>
    <a><span>Second Menu</span></a>
  </li>

  >>> secondMenu = SecondMenu(sampleContent, request, firstView)
  >>> secondMenu.update()
  >>> print(secondMenu.render())
  <li>
    <a><span>First Menu</span></a>
  </li>
  <li>
    <a><span>Second Menu</span></a>
  </li>

Now we can register a menu selector for our page whihc renders the menu
as selected if we access the page:

  >>> context = xmlconfig.string("""
  ... <configure
  ...     xmlns:z3c="http://namespaces.zope.org/z3c">
  ...   <z3c:menuSelector
  ...       view=".testing.IFirstView"
  ...       manager=".testing.IFirstMenu"
  ...       menu=".testing.FirstMenuItem"
  ...       />
  ... </configure>
  ... """, context)

After we registered a menu selector for the first view and first menu, we will
see that the first menu get rendered as selected on the first menu:

  >>> firstMenu = FirstMenu(sampleContent, request, firstView)
  >>> firstMenu.update()
  >>> print(firstMenu.render())
  <li class="selected">
    <a><span>First Menu</span></a>
  </li>
  <li>
    <a><span>Second Menu</span></a>
  </li>

But not on the second menu:

  >>> secondMenu = SecondMenu(sampleContent, request, firstView)
  >>> secondMenu.update()
  >>> print(secondMenu.render())
  <li>
    <a><span>First Menu</span></a>
  </li>
  <li>
    <a><span>Second Menu</span></a>
  </li>


=======
CHANGES
=======

2.0 (2023-02-07)
----------------

- Add support for Python 3.8, 3.9, 3.10, 3.11.

- Drop support for Python 2.7, 3.5, 3.6.

- Drop support for deprecated ``python setup.py test``.


1.1.0 (2018-10-09)
------------------

- Add support for Python 3.7.

- Remove all deprecation warnings.


1.0.0 (2017-04-17)
------------------

- Pin Python support to 2.7, 3.5, 3.6 and PyPy.


1.0.0a1 (2013-03-03)
--------------------


- Added support for Python 3.3.

- Changed ``zope.testbrowser`` tests to ``WebTest``, since ``zope.testbrowser``
  is not yet ported.

- Replaced deprecated ``zope.interface.implements`` usage with equivalent
  ``zope.interface.implementer`` decorator.

- Dropped support for Python 2.4 and 2.5.


0.8.0 (2010-07-12)
------------------

- Replaced `zope.app.pagetemplate` test dependency by
  `zope.browserpage` as the needed ``metaconfigure.registerType`` has
  been moved there lately without leaving a BBB import.


0.7.1 (2009-12-26)
------------------

- Removed dependency on ``z3c.i18n`` by declaring the z3c
  `MessageFactory` locally.

- Using python ``doctest`` module instead of
  ``zope.testing.doctestunit`` as it deprecated now.


0.7.0 (2009-11-30)
------------------

- Adjust dependencies and imports, to reflect changes in zope packages.


0.6.0 (2009-02-07)
------------------

- Replaced ``zope.app.component`` by ``zope.site``.

- Replaced ``zope.app.container`` by ``zope.container``.

- ``zope.app.pagetemplate`` is only a test dependency.


0.5.1 (2009-01-04)
------------------

- Add support for viewNames that start with ``@@``. They are now
  processed properly by the ViewNameSelectedChecker.

- Added documentation to Pypi home page.


0.5.0 (2008-04-11)
------------------

- bugfix: fixed cssInActive usage. This was broken and ended in not
  using the cssInActive CSS class argument

- added more tests, now we have 100% coverage

- make ISiteMenu only available for ISite but not for IContainmentRoot

- make template pluggable with z3c.template pattern

- initial Release

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/zopefoundation/z3c.menu.ready2go",
    "name": "z3c.menu.ready2go",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "zope3 z3c ready 2 go menu",
    "author": "Stephan Richter, Roger Ineichen and the Zope Community",
    "author_email": "zope-dev@zope.dev",
    "download_url": "https://files.pythonhosted.org/packages/42/00/56ad86d815456a1366953b7fa66aeb0c00f723f8787292690cda2165fe92/z3c.menu.ready2go-2.0.tar.gz",
    "platform": null,
    "description": "This package provides a \"ready-to-go\" menu implementation based on viewlets for \nZope 3.\n\n\n.. contents::\n\n===============\nReady 2 go Menu\n===============\n\nThe z3c.menu.ready2go package provides a menu implementation which allows you\nto implement menus based on content providers and viewlets.\n\nFirst let's setup our defualt menu item template:\n\n  >>> import os\n  >>> import zope.component\n  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer\n  >>> from zope.publisher.interfaces.browser import IBrowserView\n  >>> from z3c.template.interfaces import IContentTemplate\n  >>> from z3c.template.template import TemplateFactory\n  >>> import z3c.menu.ready2go\n  >>> baseDir = os.path.split(z3c.menu.ready2go.__file__)[0]\n  >>> itemTemplate = os.path.join(baseDir, 'item.pt')\n  >>> itemTemplateFactory = TemplateFactory(itemTemplate, 'text/html')\n  >>> zope.component.provideAdapter(itemTemplateFactory,\n  ...     (IBrowserView, IDefaultBrowserLayer), IContentTemplate)\n\n\nGlobal Menu\n-----------\n\nLet's create some menu and register them as viewlet manager:\n\n  >>> from zope.viewlet.interfaces import IViewlet\n  >>> from zope.viewlet import manager\n  >>> from z3c.menu.ready2go import interfaces\n  >>> from z3c.menu.ready2go import IGlobalMenu\n  >>> from z3c.menu.ready2go import ISiteMenu\n  >>> from z3c.menu.ready2go import IContextMenu\n  >>> from z3c.menu.ready2go import IAddMenu\n  >>> from z3c.menu.ready2go.manager import MenuManager\n\nAnd we configure our menu item as viewlet Managers. This is normaly done by the\n``viewletManager`` ZCML directive:\n\n  >>> GlobalMenu = manager.ViewletManager('left', IGlobalMenu,\n  ...     bases=(MenuManager,))\n\n  >>> SiteMenu = manager.ViewletManager('left', ISiteMenu,\n  ...     bases=(MenuManager,))\n\n  >>> ContextMenu = manager.ViewletManager('left', IContextMenu,\n  ...     bases=(MenuManager,))\n\n  >>> AddMenu = manager.ViewletManager('left', IAddMenu,\n  ...     bases=(MenuManager,))\n\nOur menu managers implement IMenuManager:\n\n  >>> interfaces.IMenuManager.implementedBy(GlobalMenu)\n  True\n\n  >>> interfaces.IMenuManager.implementedBy(SiteMenu)\n  True\n\n  >>> interfaces.IMenuManager.implementedBy(ContextMenu)\n  True\n\n  >>> interfaces.IMenuManager.implementedBy(AddMenu)\n  True\n\nWe also need our checker adapter which can check if a menu item is available\nand/or selected:\n\n  >>> from z3c.menu.ready2go import checker\n  >>> zope.component.provideAdapter(checker.GlobalSelectedChecker)\n  >>> zope.component.provideAdapter(checker.SiteSelectedChecker)\n  >>> zope.component.provideAdapter(checker.ContextSelectedChecker)\n\nNow we have to define a site and a context:\n\n  >>> import zope.interface\n  >>> from zope.container import contained, btree\n  >>> from zope.location.interfaces import IContained\n  >>> from zope.component.interfaces import IPossibleSite\n  >>> from zope.site.site import SiteManagerContainer\n  >>> from zope.site.site import LocalSiteManager\n\n  >>> @zope.interface.implementer(IPossibleSite)\n  ... class Site(btree.BTreeContainer, SiteManagerContainer):\n  ...     def __init__(self):\n  ...         super(Site, self).__init__()\n  ...         self.setSiteManager(LocalSiteManager(self))\n\n  >>> @zope.interface.implementer(IContained)\n  ... class Content(contained.Contained):\n  ...     pass\n\n  >>> root['site'] = Site()\n  >>> site = root['site']\n\nNow we have to set the site object as site. This is normaly done by the\ntraverser but we do this here with the hooks helper because we do not really\ntraaverse to the site within the publisher/traverser:\n\n  >>> from zope.component import hooks\n  >>> hooks.setSite(site)\n\n  >>> site['content'] = Content()\n  >>> content = site['content']\n\n  >>> from zope.publisher.browser import TestRequest\n  >>> request = TestRequest()\n\nAnd we need a view which knows about it's parent:\n\n  >>> @zope.interface.implementer(IBrowserView)\n  ... class View(contained.Contained):\n  ...\n  ...     def __init__(self, context, request):\n  ...         self.__parent__ = context\n  ...         self.context = context\n  ...         self.request = request\n\n  >>> view = View(content, request)\n\nOur menus can adapt the context, request and view. See IViewletManager in\nzope.viewlet for more infos about this pattern. If we render them, there is an\nempty string returned. This means the menus don't find menu items for rendering:\n\n  >>> globalMenu = GlobalMenu(content, request, view)\n  >>> globalMenu.update()\n  >>> globalMenu.render()\n  ''\n\n  >>> siteMenu = SiteMenu(content, request, view)\n  >>> siteMenu.update()\n  >>> siteMenu.render()\n  ''\n\n  >>> contextMenu = ContextMenu(content, request, view)\n  >>> contextMenu.update()\n  >>> contextMenu.render()\n  ''\n\n  >>> addMenu = AddMenu(content, request, view)\n  >>> addMenu.update()\n  >>> addMenu.render()\n  ''\n\n\nGlobal Menu Item\n----------------\n\nNow we register a context menu item for our IGlobalMenu:\n\n  >>> from z3c.menu.ready2go.item import GlobalMenuItem\n  >>> class MyGlobalMenuItem(GlobalMenuItem):\n  ...\n  ...     viewName = 'root.html'\n\nNow we need a security checker for our menu item\n\n  >>> from zope.security.checker import NamesChecker, defineChecker\n  >>> viewletChecker = NamesChecker(('update', 'render'))\n  >>> defineChecker(MyGlobalMenuItem, viewletChecker)\n\nAnd we configure our menu item for IGlobalMenu. This is normaly done by the\n``viewlet`` ZCML directive:\n\n  >>> zope.component.provideAdapter(\n  ...     MyGlobalMenuItem,\n  ...     (zope.interface.Interface, IDefaultBrowserLayer,\n  ...     IBrowserView, IGlobalMenu),\n  ...     IViewlet, name='My Global')\n\nNow let's update the menu manager and see that this manager now contains\nthe menu item:\n\n  >>> globalMenu.update()\n  >>> myGlobalMenuItem = globalMenu.viewlets[0]\n  >>> myGlobalMenuItem\n  <MyGlobalMenuItem 'My Global'>\n\nNow let's render the global menu manager and you can see that the menu item\nget rendered:\n\n  >>> print(globalMenu.render())\n  <li>\n    <a href=\"http://127.0.0.1/root.html\"><span>My Global</span></a>\n  </li>\n\n\nSite Menu Item\n--------------\n\nNow we register a context menu item for our ISiteMenu:\n\n  >>> import zope.component\n  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer\n\n  >>> from z3c.menu.ready2go.item import SiteMenuItem\n  >>> class MySiteMenuItem(SiteMenuItem):\n  ...\n  ...     viewName = 'site.html'\n\nNow we need a security checker for our menu item\n\n  >>> from zope.security.checker import NamesChecker, defineChecker\n  >>> viewletChecker = NamesChecker(('update', 'render'))\n  >>> defineChecker(MySiteMenuItem, viewletChecker)\n\nAnd we configure our menu item for ISiteMenu. This is normaly done by the\n``viewlet`` ZCML directive:\n\n  >>> zope.component.provideAdapter(\n  ...     MySiteMenuItem,\n  ...     (zope.interface.Interface, IDefaultBrowserLayer,\n  ...     IBrowserView, ISiteMenu),\n  ...     IViewlet, name='My Site')\n\nNow let's render the site menu again. You can see that we ve got a menu item\nand the url points to our site:\n\n  >>> siteMenu.update()\n  >>> print(siteMenu.render())\n  <li>\n    <a href=\"http://127.0.0.1/site/site.html\"><span>My Site</span></a>\n  </li>\n\n\nContext Menu Item\n-----------------\n\nNow we register a context menu item for our IContextMenu:\n\n  >>> import zope.component\n  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer\n\n  >>> from z3c.menu.ready2go.item import ContextMenuItem\n  >>> class MyContextMenuItem(ContextMenuItem):\n  ...     viewName = 'context.html'\n  ...     weight = 1\n\nNow we need a security checker for our menu item\n\n  >>> from zope.security.checker import NamesChecker, defineChecker\n  >>> viewletChecker = NamesChecker(('update', 'render'))\n  >>> defineChecker(MyContextMenuItem, viewletChecker)\n\nAnd we configure our menu item for IContextMenu. This is normaly done by the\n``viewlet`` ZCML directive:\n\n  >>> zope.component.provideAdapter(\n  ...     MyContextMenuItem,\n  ...     (zope.interface.Interface, IDefaultBrowserLayer,\n  ...     IBrowserView, IContextMenu),\n  ...     IViewlet, name='My Context')\n\nNow let's render the context menu again. You can see that we ve got a menu\nitem. Another important point here is, that the url of such ContextMemuItem\nimplementations point to the context of the view:\n\n  >>> contextMenu.update()\n  >>> print(contextMenu.render())\n  <li>\n    <a href=\"http://127.0.0.1/site/content/context.html\"><span>My Context</span></a>\n  </li>\n\nLet's set the view  __name__ to ``context.html``. This will reflect that\nthe view offers the same name that our context menu needs to get rendered as\nselected:\n\n  >>> view.__name__ = 'context.html'\n\nNow try again and see if the context menu item get rendered as selected:\n\n  >>> contextMenu.update()\n  >>> print(contextMenu.render())\n  <li class=\"selected\">\n    <a href=\"http://127.0.0.1/site/content/context.html\"><span>My Context</span></a>\n  </li>\n\nAlso, let's check that menu item is marked selected even if we provided a viewName in\nthe ``@@context.html`` form:\n\n  >>> MyContextMenuItem.viewName = '@@context.html'\n  >>> contextMenu.update()\n  >>> print(contextMenu.render())\n  <li class=\"selected\">\n    <a href=\"http://127.0.0.1/site/content/@@context.html\"><span>My Context</span></a>\n  </li>\n\nOkay, change viewName back to ``context.html`` for further tests:\n\n  >>> MyContextMenuItem.viewName = 'context.html'\n\nNow add a second context menu item and check if we can use the cssInActive\nargument which is normaly a empty string:\n\n  >>> class InActiveMenuItem(ContextMenuItem):\n  ...     viewName = 'inActive.html'\n  ...     cssInActive = 'inActive'\n  ...     weight = 2\n\n  >>> defineChecker(InActiveMenuItem, viewletChecker)\n\n  >>> zope.component.provideAdapter(\n  ...     InActiveMenuItem,\n  ...     (zope.interface.Interface, IDefaultBrowserLayer,\n  ...     IBrowserView, IContextMenu),\n  ...     IViewlet, name='In Active')\n\nNow update and render again:\n\n  >>> contextMenu.update()\n  >>> print(contextMenu.render())\n  <li class=\"selected\">\n    <a href=\"http://127.0.0.1/site/content/context.html\"><span>My Context</span></a>\n  </li>\n  <li class=\"inActive\">\n    <a href=\"http://127.0.0.1/site/content/inActive.html\"><span>In Active</span></a>\n  </li>\n\nAddMenu\n-------\n\nThe add menu can be used for offering links to any kind of add forms per\ncontext. This allows us to offer independent add form links doesn't matter which\nform framework is used. Let's now define such a simple AddMenuItem pointing\nto a add form url. Not; the add form and it's url do not exist in thsi test.\nThis aslo means there is no guarantee that a form exist if a add menu item\nis configured.\n\n  >>> from z3c.menu.ready2go.item import AddMenuItem\n  >>> class MyAddMenuItem(AddMenuItem):\n  ...\n  ...     viewName = 'addSomething.html'\n\nNow we need a security checker for our menu item\n\n  >>> from zope.security.checker import NamesChecker, defineChecker\n  >>> viewletChecker = NamesChecker(('update', 'render'))\n  >>> defineChecker(MyAddMenuItem, viewletChecker)\n\nAnd we configure our menu item for IAddMenu. This is normaly done by the\n``viewlet`` ZCML directive:\n\n  >>> zope.component.provideAdapter(\n  ...     MyAddMenuItem,\n  ...     (zope.interface.Interface, IDefaultBrowserLayer,\n  ...     IBrowserView, IAddMenu),\n  ...     IViewlet, name='My AddMenu')\n\nNow we can update and render our add menu:\n\n  >>> addMenu.update()\n  >>> print(addMenu.render())\n  <li>\n    <a href=\"http://127.0.0.1/site/content/addSomething.html\"><span>My AddMenu</span></a>\n  </li>\n\n\nMenu groups\n-----------\n\nThe global and the site menu items are grouped menu items. This means such menu\nitems should get rendered as selected if a context menu item is selected. This\nreflects the menu hierarchie. Let's show how we can solve this not so simple\nproblem. We offer a ISelectedChecker adapter which can decide if a menu get\nrendered as selected or not. This is very usefull because normaly a menu get\nregistered and later we add views and can not change the menu item\nimplementation. Let's see how such an adapter can handle an existing menu,\ncontext and view setup and change the selected rendering. We register a\nselected checker for our site menu item:\n\n  >>> zope.component.provideAdapter(checker.TrueSelectedChecker,\n  ...     (IContained, IDefaultBrowserLayer, None, ISiteMenu, MySiteMenuItem),\n  ...     interfaces.ISelectedChecker)\n\nNow we can render the site menu again. Note that our context is still the\nsample content object.\n\n  >>> siteMenu.update()\n  >>> print(siteMenu.render())\n  <li class=\"selected\">\n    <a href=\"http://127.0.0.1/site/site.html\"><span>My Site</span></a>\n  </li>\n\nThis reflects that the site menu is a group menu which the context menu item\nof the content object is selected too.\n\n  >>> contextMenu.update()\n  >>> print(contextMenu.render())\n  <li class=\"selected\">\n    <a href=\"http://127.0.0.1/site/content/context.html\"><span>My Context</span></a>\n  </li>\n  <li class=\"inActive\">\n    <a href=\"http://127.0.0.1/site/content/inActive.html\"><span>In Active</span></a>\n  </li>\n\n\nEmptyMenuManager\n----------------\n\nThere is a empty menu manager whihc could be used for override existing\nmenu managers.\n\n  >>> from z3c.menu.ready2go.manager import EmptyMenuManager\n  >>> emptyMenu = EmptyMenuManager(None, None, None)\n\nOur empty menu manager implements ``IMenuManager``:\n\n  >>> interfaces.IMenuManager.providedBy(emptyMenu)\n  True\n\nThis empty menu manager returns allways an empty string if we render them:\n\n  >>> emptyMenu.update()\n  >>> emptyMenu.render()\n  ''\n\n\nSpecial use case\n----------------\n\nWe have some special use case because of Zope's internals. One important part\nis that our menu heavy depend on context and it's __parent__ chain to the\nzope application root. This is not allways supported by Zopes default setup.\nOne part is the bad integrated application control part which fakes a root\nobject which doesn't know about the real childs of the real root from the\nZODB e.g. application root. Now we will show you that our menu by default\nrender no items if we get such a fake root which messes up our menu structure.\n\nLet's define a object which does not know about any __parent__.\n\n  >>> nirvana = Content()\n  >>> nirvanaView = View(nirvana, request)\n\nNow we can check what's happen to the menus if we adapt the parent less nirvana\ncontext and update and render the menus. You can see that the global menu does\nnot contain any menu item. That's because the global menu items tries to find\nthe root by traversing from the context to the root by the __parent__ chain\nand we don't support any parent for our nirvana object:\n\n  >>> globalMenu = GlobalMenu(nirvana, request, nirvanaView)\n  >>> globalMenu.update()\n  >>> globalMenu.render()\n  ''\n\nAlso the SiteMenu doesn't contain any menu item because of the parent less\nobject:\n\n  >>> siteMenu = SiteMenu(nirvana, request, nirvanaView)\n  >>> siteMenu.update()\n  >>> siteMenu.render()\n  ''\n\n  >>> contextMenu = ContextMenu(nirvana, request, nirvanaView)\n  >>> contextMenu.update()\n  >>> contextMenu.render()\n  ''\n\n  >>> addMenu = AddMenu(nirvana, request, nirvanaView)\n  >>> addMenu.update()\n  >>> addMenu.render()\n  ''\n\n\n===================\nZ3C Menu directives\n===================\n\nShow how we can use the menu directive. Register the meta configuration for \nthe directive.\n\nFirst let's setup our defualt menu item template first:\n\n  >>> import os\n  >>> import zope.component\n  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer\n  >>> from zope.publisher.interfaces.browser import IBrowserView\n  >>> from z3c.template.interfaces import IContentTemplate\n  >>> from z3c.template.template import TemplateFactory\n  >>> import z3c.menu.ready2go\n  >>> baseDir = os.path.split(z3c.menu.ready2go.__file__)[0]\n  >>> itemTemplate = os.path.join(baseDir, 'item.pt')\n  >>> itemTemplateFactory = TemplateFactory(itemTemplate, 'text/html')\n  >>> zope.component.provideAdapter(itemTemplateFactory,\n  ...     (IBrowserView, IDefaultBrowserLayer), IContentTemplate)\n\n  >>> import sys\n  >>> from zope.configuration import xmlconfig\n  >>> import z3c.menu.ready2go\n  >>> context = xmlconfig.file('meta.zcml', z3c.menu.ready2go)\n\nWe need to register our checker adapter which can check if a menu item is \nselected or not:\n\n  >>> import zope.component\n  >>> from z3c.menu.ready2go import checker\n  >>> zope.component.provideAdapter(checker.ContextSelectedChecker)\n\nLet's define a content object:\n\n  >>> from z3c.menu.ready2go import testing\n  >>> sampleContent = testing.Sample('Sample Content')\n\nNow add the content object to our site root:\n\n  >>> root['sample'] = sampleContent\n\nNow we can define our test menu manager:\n\n  >>> from zope.viewlet.manager import ViewletManager\n  >>> from z3c.menu.ready2go import manager\n  >>> FirstMenu = ViewletManager('left', testing.IFirstMenu,\n  ...     bases=(manager.MenuManager,))\n\n  >>> SecondMenu = ViewletManager('left', testing.ISecondMenu,\n  ...     bases=(manager.MenuManager,))\n\nAnd we need a view which knows about it's parent:\n\n  >>> from zope.publisher.browser import TestRequest\n  >>> request = TestRequest()\n  >>> firstView = testing.FirstView(sampleContent, request)\n  >>> testing.IFirstView.providedBy(firstView)\n  True\n\n  >>> secondView = testing.SecondView(sampleContent, request)\n  >>> testing.ISecondView.providedBy(secondView)\n  True\n\nAs you can see the menu is not selected if we access the page:\n\n  >>> firstMenu = FirstMenu(sampleContent, request, firstView)\n  >>> testing.IFirstMenu.providedBy(firstMenu)\n  True\n\n  >>> firstMenu.update()\n  >>> firstMenu.render()\n  ''\n\n  >>> secondMenu = SecondMenu(sampleContent, request, secondView)\n  >>> testing.ISecondMenu.providedBy(secondMenu)\n  True\n\n  >>> secondMenu.update()\n  >>> secondMenu.render()\n  ''\n\nNow we need some menu items for the first menu:\n\n  >>> from zope.publisher.interfaces.browser import IBrowserView\n  >>> from zope.publisher.interfaces.browser import IBrowserRequest\n  >>> from zope.viewlet.interfaces import IViewlet\n  >>> zope.component.provideAdapter(\n  ...     testing.FirstMenuItem,\n  ...     (zope.interface.Interface, IBrowserRequest,\n  ...     IBrowserView, testing.IFirstMenu),\n  ...     IViewlet, name='First Menu')\n\n  >>> zope.component.provideAdapter(\n  ...     testing.SecondMenuItem,\n  ...     (zope.interface.Interface, IBrowserRequest,\n  ...     IBrowserView, testing.IFirstMenu),\n  ...     IViewlet, name='Second Menu')\n\nAnd we need some menu items for the second menu:\n\n  >>> zope.component.provideAdapter(\n  ...     testing.FirstMenuItem,\n  ...     (zope.interface.Interface, IBrowserRequest,\n  ...     IBrowserView, testing.ISecondMenu),\n  ...     IViewlet, name='First Menu')\n\n  >>> zope.component.provideAdapter(\n  ...     testing.SecondMenuItem,\n  ...     (zope.interface.Interface, IBrowserRequest,\n  ...     IBrowserView, testing.ISecondMenu),\n  ...     IViewlet, name='Second Menu')\n\nNow render the menu manager again and you can see that we've got some menu\nitems. but you can see that this menu items are not selected:\n\n  >>> firstMenu = FirstMenu(sampleContent, request, firstView)\n  >>> firstMenu.update()\n  >>> print(firstMenu.render())\n  <li>\n    <a><span>First Menu</span></a>\n  </li>\n  <li>\n    <a><span>Second Menu</span></a>\n  </li>\n\n  >>> secondMenu = SecondMenu(sampleContent, request, firstView)\n  >>> secondMenu.update()\n  >>> print(secondMenu.render())\n  <li>\n    <a><span>First Menu</span></a>\n  </li>\n  <li>\n    <a><span>Second Menu</span></a>\n  </li>\n\nNow we can register a menu selector for our page whihc renders the menu\nas selected if we access the page:\n\n  >>> context = xmlconfig.string(\"\"\"\n  ... <configure\n  ...     xmlns:z3c=\"http://namespaces.zope.org/z3c\">\n  ...   <z3c:menuSelector\n  ...       view=\".testing.IFirstView\"\n  ...       manager=\".testing.IFirstMenu\"\n  ...       menu=\".testing.FirstMenuItem\"\n  ...       />\n  ... </configure>\n  ... \"\"\", context)\n\nAfter we registered a menu selector for the first view and first menu, we will\nsee that the first menu get rendered as selected on the first menu:\n\n  >>> firstMenu = FirstMenu(sampleContent, request, firstView)\n  >>> firstMenu.update()\n  >>> print(firstMenu.render())\n  <li class=\"selected\">\n    <a><span>First Menu</span></a>\n  </li>\n  <li>\n    <a><span>Second Menu</span></a>\n  </li>\n\nBut not on the second menu:\n\n  >>> secondMenu = SecondMenu(sampleContent, request, firstView)\n  >>> secondMenu.update()\n  >>> print(secondMenu.render())\n  <li>\n    <a><span>First Menu</span></a>\n  </li>\n  <li>\n    <a><span>Second Menu</span></a>\n  </li>\n\n\n=======\nCHANGES\n=======\n\n2.0 (2023-02-07)\n----------------\n\n- Add support for Python 3.8, 3.9, 3.10, 3.11.\n\n- Drop support for Python 2.7, 3.5, 3.6.\n\n- Drop support for deprecated ``python setup.py test``.\n\n\n1.1.0 (2018-10-09)\n------------------\n\n- Add support for Python 3.7.\n\n- Remove all deprecation warnings.\n\n\n1.0.0 (2017-04-17)\n------------------\n\n- Pin Python support to 2.7, 3.5, 3.6 and PyPy.\n\n\n1.0.0a1 (2013-03-03)\n--------------------\n\n\n- Added support for Python 3.3.\n\n- Changed ``zope.testbrowser`` tests to ``WebTest``, since ``zope.testbrowser``\n  is not yet ported.\n\n- Replaced deprecated ``zope.interface.implements`` usage with equivalent\n  ``zope.interface.implementer`` decorator.\n\n- Dropped support for Python 2.4 and 2.5.\n\n\n0.8.0 (2010-07-12)\n------------------\n\n- Replaced `zope.app.pagetemplate` test dependency by\n  `zope.browserpage` as the needed ``metaconfigure.registerType`` has\n  been moved there lately without leaving a BBB import.\n\n\n0.7.1 (2009-12-26)\n------------------\n\n- Removed dependency on ``z3c.i18n`` by declaring the z3c\n  `MessageFactory` locally.\n\n- Using python ``doctest`` module instead of\n  ``zope.testing.doctestunit`` as it deprecated now.\n\n\n0.7.0 (2009-11-30)\n------------------\n\n- Adjust dependencies and imports, to reflect changes in zope packages.\n\n\n0.6.0 (2009-02-07)\n------------------\n\n- Replaced ``zope.app.component`` by ``zope.site``.\n\n- Replaced ``zope.app.container`` by ``zope.container``.\n\n- ``zope.app.pagetemplate`` is only a test dependency.\n\n\n0.5.1 (2009-01-04)\n------------------\n\n- Add support for viewNames that start with ``@@``. They are now\n  processed properly by the ViewNameSelectedChecker.\n\n- Added documentation to Pypi home page.\n\n\n0.5.0 (2008-04-11)\n------------------\n\n- bugfix: fixed cssInActive usage. This was broken and ended in not\n  using the cssInActive CSS class argument\n\n- added more tests, now we have 100% coverage\n\n- make ISiteMenu only available for ISite but not for IContainmentRoot\n\n- make template pluggable with z3c.template pattern\n\n- initial Release\n",
    "bugtrack_url": null,
    "license": "ZPL 2.1",
    "summary": "A ready to go menu for Zope3",
    "version": "2.0",
    "split_keywords": [
        "zope3",
        "z3c",
        "ready",
        "2",
        "go",
        "menu"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "59ea07da56edbcbf8b17c44ca7386fa507734713bd2c8be87db5a5ca5429267b",
                "md5": "543f7bb84fe19e29d488edd50e4cb44c",
                "sha256": "45f73babf7b651ef0e02dfe38b719a8f1d89cb9f46bde1d02fcafe012021794e"
            },
            "downloads": -1,
            "filename": "z3c.menu.ready2go-2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "543f7bb84fe19e29d488edd50e4cb44c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 27263,
            "upload_time": "2023-02-07T10:10:01",
            "upload_time_iso_8601": "2023-02-07T10:10:01.256570Z",
            "url": "https://files.pythonhosted.org/packages/59/ea/07da56edbcbf8b17c44ca7386fa507734713bd2c8be87db5a5ca5429267b/z3c.menu.ready2go-2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "420056ad86d815456a1366953b7fa66aeb0c00f723f8787292690cda2165fe92",
                "md5": "79533fe77723c5894895092364c4a1d8",
                "sha256": "89fb65d1ca19d830ce247ffb2572ed68646be4a7d4509b8253e523961abd2f59"
            },
            "downloads": -1,
            "filename": "z3c.menu.ready2go-2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "79533fe77723c5894895092364c4a1d8",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 26476,
            "upload_time": "2023-02-07T10:10:03",
            "upload_time_iso_8601": "2023-02-07T10:10:03.661276Z",
            "url": "https://files.pythonhosted.org/packages/42/00/56ad86d815456a1366953b7fa66aeb0c00f723f8787292690cda2165fe92/z3c.menu.ready2go-2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-02-07 10:10:03",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "zopefoundation",
    "github_project": "z3c.menu.ready2go",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "z3c.menu.ready2go"
}
        
Elapsed time: 0.04218s