add basic support for menu handler
default menu is not yet useful, but the handler mostly works. except for actual auth/perm checks since we have no users yet!
This commit is contained in:
parent
001179c87f
commit
60e8303d29
|
@ -9,6 +9,7 @@
|
|||
|
||||
app
|
||||
helpers
|
||||
menus
|
||||
static
|
||||
subscribers
|
||||
util
|
||||
|
|
6
docs/api/wuttaweb/menus.rst
Normal file
6
docs/api/wuttaweb/menus.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
``wuttaweb.menus``
|
||||
==================
|
||||
|
||||
.. automodule:: wuttaweb.menus
|
||||
:members:
|
|
@ -48,6 +48,10 @@ tests = ["pytest-cov", "tox"]
|
|||
main = "wuttaweb.app:main"
|
||||
|
||||
|
||||
[project.entry-points."wutta.providers"]
|
||||
wuttaweb = "wuttaweb.app:WebAppProvider"
|
||||
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://rattailproject.org/"
|
||||
Repository = "https://kallithea.rattailproject.org/rattail-project/wuttaweb"
|
||||
|
|
|
@ -26,11 +26,38 @@ Application
|
|||
|
||||
import os
|
||||
|
||||
from wuttjamaican.app import AppProvider
|
||||
from wuttjamaican.conf import make_config
|
||||
|
||||
from pyramid.config import Configurator
|
||||
|
||||
|
||||
class WebAppProvider(AppProvider):
|
||||
"""
|
||||
The :term:`app provider<app provider>` for WuttaWeb. This adds
|
||||
some methods to get web-specific :term:`handlers<handler>`.
|
||||
"""
|
||||
|
||||
def get_web_menu_handler(self, **kwargs):
|
||||
"""
|
||||
Get the configured "menu" handler for the web app.
|
||||
|
||||
Specify a custom handler in your config file like this:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[wuttaweb]
|
||||
menus.handler_spec = poser.web.menus:PoserMenuHandler
|
||||
|
||||
:returns: Instance of :class:`~wuttaweb.menus.MenuHandler`.
|
||||
"""
|
||||
if 'web_menu_handler' not in self.__dict__:
|
||||
spec = self.config.get('wuttaweb.menus.handler_spec',
|
||||
default='wuttaweb.menus:MenuHandler')
|
||||
self.web_menu_handler = self.app.load_object(spec)(self.config)
|
||||
return self.web_menu_handler
|
||||
|
||||
|
||||
def make_wutta_config(settings):
|
||||
"""
|
||||
Make a WuttaConfig object from the given settings.
|
||||
|
|
297
src/wuttaweb/menus.py
Normal file
297
src/wuttaweb/menus.py
Normal file
|
@ -0,0 +1,297 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
#
|
||||
# Wutta Framework is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||
# later version.
|
||||
#
|
||||
# Wutta Framework is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Main Menu
|
||||
"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
|
||||
from wuttjamaican.app import GenericHandler
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MenuHandler(GenericHandler):
|
||||
"""
|
||||
Base class and default implementation for menu handler.
|
||||
|
||||
It is assumed that most apps will override the menu handler with
|
||||
their own subclass. In particular the subclass will override
|
||||
:meth:`make_menus()` and/or :meth:`make_admin_menu()`.
|
||||
|
||||
The app should normally not instantiate the menu handler directly,
|
||||
but instead call
|
||||
:meth:`~wuttaweb.app.WebAppProvider.get_web_menu_handler()` on the
|
||||
:term:`app handler`.
|
||||
|
||||
To configure your menu handler to be used, do this within your
|
||||
:term:`config extension`::
|
||||
|
||||
config.setdefault('wuttaweb.menus.handler_spec', 'poser.web.menus:PoserMenuHandler')
|
||||
|
||||
The core web app will call :meth:`do_make_menus()` to get the
|
||||
final (possibly filtered) menu set for the current user. The
|
||||
menu set should be a list of dicts, for example::
|
||||
|
||||
menus = [
|
||||
{
|
||||
'title': "First Dropdown",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{
|
||||
'title': "Foo",
|
||||
'route': 'foo',
|
||||
},
|
||||
{'type': 'sep'}, # horizontal line
|
||||
{
|
||||
'title': "Bar",
|
||||
'route': 'bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Second Dropdown",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{
|
||||
'title': "Wikipedia",
|
||||
'url': 'https://en.wikipedia.org',
|
||||
'target': '_blank',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
"""
|
||||
|
||||
##############################
|
||||
# default menu definitions
|
||||
##############################
|
||||
|
||||
def make_menus(self, request, **kwargs):
|
||||
"""
|
||||
Generate the full set of menus for the app.
|
||||
|
||||
This method provides a semi-sane menu set by default, but it
|
||||
is expected for most apps to override it.
|
||||
|
||||
The return value should be a list of dicts as described above.
|
||||
"""
|
||||
return [
|
||||
self.make_admin_menu(request),
|
||||
]
|
||||
|
||||
def make_admin_menu(self, request, **kwargs):
|
||||
"""
|
||||
Generate a typical Admin menu.
|
||||
|
||||
This method provides a semi-sane menu set by default, but it
|
||||
is expected for most apps to override it.
|
||||
|
||||
The return value for this method should be a *single* dict,
|
||||
which will ultimately be one element of the final list of
|
||||
dicts as described above.
|
||||
"""
|
||||
return {
|
||||
'title': "Admin",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{
|
||||
'title': "TODO!",
|
||||
'url': '#',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
##############################
|
||||
# default internal logic
|
||||
##############################
|
||||
|
||||
def do_make_menus(self, request, **kwargs):
|
||||
"""
|
||||
This method is responsible for constructing the final menu
|
||||
set. It first calls :meth:`make_menus()` to get the basic
|
||||
set, and then it prunes entries as needed based on current
|
||||
user permissions.
|
||||
|
||||
The web app calls this method but you normally should not need
|
||||
to override it; you can override :meth:`make_menus()` instead.
|
||||
"""
|
||||
raw_menus = self.make_menus(request, **kwargs)
|
||||
|
||||
# now we have "simple" (raw) menus definition, but must refine
|
||||
# that somewhat to produce our final menus
|
||||
self._mark_allowed(request, raw_menus)
|
||||
final_menus = []
|
||||
for topitem in raw_menus:
|
||||
|
||||
if topitem['allowed']:
|
||||
|
||||
if topitem.get('type') == 'link':
|
||||
final_menus.append(self._make_menu_entry(request, topitem))
|
||||
|
||||
else: # assuming 'menu' type
|
||||
|
||||
menu_items = []
|
||||
for item in topitem['items']:
|
||||
if not item['allowed']:
|
||||
continue
|
||||
|
||||
# nested submenu
|
||||
if item.get('type') == 'menu':
|
||||
submenu_items = []
|
||||
for subitem in item['items']:
|
||||
if subitem['allowed']:
|
||||
submenu_items.append(self._make_menu_entry(request, subitem))
|
||||
menu_items.append({
|
||||
'type': 'submenu',
|
||||
'title': item['title'],
|
||||
'items': submenu_items,
|
||||
'is_menu': True,
|
||||
'is_sep': False,
|
||||
})
|
||||
|
||||
elif item.get('type') == 'sep':
|
||||
# we only want to add a sep, *if* we already have some
|
||||
# menu items (i.e. there is something to separate)
|
||||
# *and* the last menu item is not a sep (avoid doubles)
|
||||
if menu_items and not menu_items[-1]['is_sep']:
|
||||
menu_items.append(self._make_menu_entry(request, item))
|
||||
|
||||
else: # standard menu item
|
||||
menu_items.append(self._make_menu_entry(request, item))
|
||||
|
||||
# remove final separator if present
|
||||
if menu_items and menu_items[-1]['is_sep']:
|
||||
menu_items.pop()
|
||||
|
||||
# only add if we wound up with something
|
||||
assert menu_items
|
||||
if menu_items:
|
||||
group = {
|
||||
'type': 'menu',
|
||||
'key': topitem.get('key'),
|
||||
'title': topitem['title'],
|
||||
'items': menu_items,
|
||||
'is_menu': True,
|
||||
'is_link': False,
|
||||
}
|
||||
|
||||
# topitem w/ no key likely means it did not come
|
||||
# from config but rather explicit definition in
|
||||
# code. so we are free to "invent" a (safe) key
|
||||
# for it, since that is only for editing config
|
||||
if not group['key']:
|
||||
group['key'] = self._make_menu_key(topitem['title'])
|
||||
|
||||
final_menus.append(group)
|
||||
|
||||
return final_menus
|
||||
|
||||
def _is_allowed(self, request, item):
|
||||
"""
|
||||
Logic to determine if a given menu item is "allowed" for
|
||||
current user.
|
||||
"""
|
||||
perm = item.get('perm')
|
||||
# TODO
|
||||
# if perm:
|
||||
# return request.has_perm(perm)
|
||||
return True
|
||||
|
||||
def _mark_allowed(self, request, menus):
|
||||
"""
|
||||
Traverse the menu set, and mark each item as "allowed" (or
|
||||
not) based on current user permissions.
|
||||
"""
|
||||
for topitem in menus:
|
||||
|
||||
if topitem.get('type', 'menu') == 'link':
|
||||
topitem['allowed'] = True
|
||||
|
||||
elif topitem.get('type', 'menu') == 'menu':
|
||||
topitem['allowed'] = False
|
||||
|
||||
for item in topitem['items']:
|
||||
|
||||
if item.get('type') == 'menu':
|
||||
for subitem in item['items']:
|
||||
subitem['allowed'] = self._is_allowed(request, subitem)
|
||||
|
||||
item['allowed'] = False
|
||||
for subitem in item['items']:
|
||||
if subitem['allowed'] and subitem.get('type') != 'sep':
|
||||
item['allowed'] = True
|
||||
break
|
||||
|
||||
else:
|
||||
item['allowed'] = self._is_allowed(request, item)
|
||||
|
||||
for item in topitem['items']:
|
||||
if item['allowed'] and item.get('type') != 'sep':
|
||||
topitem['allowed'] = True
|
||||
break
|
||||
|
||||
def _make_menu_entry(self, request, item):
|
||||
"""
|
||||
Convert a simple menu entry dict, into a proper menu-related
|
||||
object, for use in constructing final menu.
|
||||
"""
|
||||
# separator
|
||||
if item.get('type') == 'sep':
|
||||
return {
|
||||
'type': 'sep',
|
||||
'is_menu': False,
|
||||
'is_sep': True,
|
||||
}
|
||||
|
||||
# standard menu item
|
||||
entry = {
|
||||
'type': 'item',
|
||||
'title': item['title'],
|
||||
'perm': item.get('perm'),
|
||||
'target': item.get('target'),
|
||||
'is_link': True,
|
||||
'is_menu': False,
|
||||
'is_sep': False,
|
||||
}
|
||||
if item.get('route'):
|
||||
entry['route'] = item['route']
|
||||
try:
|
||||
entry['url'] = request.route_url(entry['route'])
|
||||
except KeyError: # happens if no such route
|
||||
log.warning("invalid route name for menu entry: %s", entry)
|
||||
entry['url'] = entry['route']
|
||||
entry['key'] = entry['route']
|
||||
else:
|
||||
if item.get('url'):
|
||||
entry['url'] = item['url']
|
||||
entry['key'] = self._make_menu_key(entry['title'])
|
||||
return entry
|
||||
|
||||
def _make_menu_key(self, value):
|
||||
"""
|
||||
Generate a normalized menu key for the given value.
|
||||
"""
|
||||
return re.sub(r'\W', '', value.lower())
|
|
@ -115,6 +115,12 @@ def before_render(event):
|
|||
|
||||
Reference to the built-in module, :mod:`python:json`.
|
||||
|
||||
.. data:: 'menus'
|
||||
|
||||
Set of entries to be shown in the main menu. This is obtained
|
||||
by calling :meth:`~wuttaweb.menus.MenuHandler.do_make_menus()`
|
||||
on the configured :class:`~wuttaweb.menus.MenuHandler`.
|
||||
|
||||
.. data:: 'url'
|
||||
|
||||
Reference to the request method,
|
||||
|
@ -131,8 +137,8 @@ def before_render(event):
|
|||
context['url'] = request.route_url
|
||||
context['json'] = json
|
||||
|
||||
# TODO
|
||||
context['menus'] = []
|
||||
menus = app.get_web_menu_handler()
|
||||
context['menus'] = menus.do_make_menus(request)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
|
|
@ -103,6 +103,11 @@
|
|||
}
|
||||
% endif
|
||||
|
||||
/* nb. this refers to the "menu-sized" app title in far left of main menu */
|
||||
#global-header-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#current-context {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,6 @@
|
|||
|
||||
<%def name="footer()">
|
||||
<p class="has-text-centered">
|
||||
powered by ${h.link_to("Wutta Framework", 'https://pypi.org/project/WuttJamaican/', target='_blank')}
|
||||
powered by ${h.link_to("WuttaWeb", 'https://wuttaproject.org/', target='_blank')}
|
||||
</p>
|
||||
</%def>
|
||||
|
|
315
tests/test_menus.py
Normal file
315
tests/test_menus.py
Normal file
|
@ -0,0 +1,315 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
|
||||
from pyramid import testing
|
||||
|
||||
from wuttaweb import menus as mod
|
||||
|
||||
|
||||
class TestMenuHandler(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig()
|
||||
self.app = self.config.get_app()
|
||||
self.handler = mod.MenuHandler(self.config)
|
||||
self.request = testing.DummyRequest()
|
||||
|
||||
def test_make_admin_menu(self):
|
||||
menus = self.handler.make_admin_menu(self.request)
|
||||
self.assertIsInstance(menus, dict)
|
||||
|
||||
def test_make_menus(self):
|
||||
menus = self.handler.make_menus(self.request)
|
||||
self.assertIsInstance(menus, list)
|
||||
|
||||
def test_is_allowed(self):
|
||||
# TODO: this should test auth/perm handling
|
||||
item = {}
|
||||
self.assertTrue(self.handler._is_allowed(self.request, item))
|
||||
|
||||
def test_mark_allowed(self):
|
||||
|
||||
def make_menus():
|
||||
return [
|
||||
{
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
mock_is_allowed = MagicMock()
|
||||
with patch.object(self.handler, '_is_allowed', new=mock_is_allowed):
|
||||
|
||||
# all should be allowed
|
||||
mock_is_allowed.return_value = True
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertTrue(menu['allowed'])
|
||||
foo, bar = menu['items']
|
||||
self.assertTrue(foo['allowed'])
|
||||
self.assertTrue(bar['allowed'])
|
||||
|
||||
# none should be allowed
|
||||
mock_is_allowed.return_value = False
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertFalse(menu['allowed'])
|
||||
foo, bar = menu['items']
|
||||
self.assertFalse(foo['allowed'])
|
||||
self.assertFalse(bar['allowed'])
|
||||
|
||||
def test_mark_allowed_submenu(self):
|
||||
|
||||
def make_menus():
|
||||
return [
|
||||
{
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Bar", 'url': '#'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
mock_is_allowed = MagicMock()
|
||||
with patch.object(self.handler, '_is_allowed', new=mock_is_allowed):
|
||||
|
||||
# all should be allowed
|
||||
mock_is_allowed.return_value = True
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertTrue(menu['allowed'])
|
||||
foo, submenu = menu['items']
|
||||
self.assertTrue(foo['allowed'])
|
||||
self.assertTrue(submenu['allowed'])
|
||||
subitem = submenu['items'][0]
|
||||
self.assertTrue(subitem['allowed'])
|
||||
|
||||
# none should be allowed
|
||||
mock_is_allowed.return_value = False
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertFalse(menu['allowed'])
|
||||
foo, submenu = menu['items']
|
||||
self.assertFalse(foo['allowed'])
|
||||
self.assertFalse(submenu['allowed'])
|
||||
subitem = submenu['items'][0]
|
||||
self.assertFalse(subitem['allowed'])
|
||||
|
||||
def test_make_menu_key(self):
|
||||
self.assertEqual(self.handler._make_menu_key('foo'), 'foo')
|
||||
self.assertEqual(self.handler._make_menu_key('FooBar'), 'foobar')
|
||||
self.assertEqual(self.handler._make_menu_key('Foo - $#Bar'), 'foobar')
|
||||
self.assertEqual(self.handler._make_menu_key('Foo__Bar'), 'foo__bar')
|
||||
|
||||
def test_make_menu_entry_item(self):
|
||||
item = {'title': "Foo", 'url': '#'}
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
self.assertEqual(entry['title'], "Foo")
|
||||
self.assertEqual(entry['url'], '#')
|
||||
self.assertTrue(entry['is_link'])
|
||||
|
||||
def test_make_menu_entry_item_with_no_url(self):
|
||||
item = {'title': "Foo"}
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
self.assertEqual(entry['title'], "Foo")
|
||||
self.assertNotIn('url', entry)
|
||||
# nb. still sets is_link = True; basically it's <a> with no href
|
||||
self.assertTrue(entry['is_link'])
|
||||
|
||||
def test_make_menu_entry_item_with_known_route(self):
|
||||
item = {'title': "Foo", 'route': 'home'}
|
||||
with patch.object(self.request, 'route_url', return_value='/something'):
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
self.assertEqual(entry['url'], '/something')
|
||||
self.assertTrue(entry['is_link'])
|
||||
|
||||
def test_make_menu_entry_item_with_unknown_route(self):
|
||||
item = {'title': "Foo", 'route': 'home'}
|
||||
with patch.object(self.request, 'route_url', side_effect=KeyError):
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
# nb. fake url is used, based on (bad) route name
|
||||
self.assertEqual(entry['url'], 'home')
|
||||
self.assertTrue(entry['is_link'])
|
||||
|
||||
def test_make_menu_entry_sep(self):
|
||||
item = {'type': 'sep'}
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'sep')
|
||||
self.assertTrue(entry['is_sep'])
|
||||
self.assertFalse(entry['is_menu'])
|
||||
|
||||
def test_do_make_menus_prune_unallowed_item(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
def is_allowed(request, item):
|
||||
if item.get('title') == 'Bar':
|
||||
return False
|
||||
return True
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', side_effect=is_allowed):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# Foo remains but Bar is pruned
|
||||
menu = menus[0]
|
||||
self.assertEqual(len(menu['items']), 1)
|
||||
item = menu['items'][0]
|
||||
self.assertEqual(item['title'], 'Foo')
|
||||
|
||||
def test_do_make_menus_prune_unallowed_menu(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Second Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Baz", 'url': '#'},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
def is_allowed(request, item):
|
||||
if item.get('title') == 'Baz':
|
||||
return True
|
||||
return False
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', side_effect=is_allowed):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# Second/Baz remains but First/Foo/Bar are pruned
|
||||
self.assertEqual(len(menus), 1)
|
||||
menu = menus[0]
|
||||
self.assertEqual(menu['title'], 'Second Menu')
|
||||
self.assertEqual(len(menu['items']), 1)
|
||||
item = menu['items'][0]
|
||||
self.assertEqual(item['title'], 'Baz')
|
||||
|
||||
def test_do_make_menus_with_top_link(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Second Link",
|
||||
'type': 'link',
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', return_value=True):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# ensure top link remains
|
||||
self.assertEqual(len(menus), 2)
|
||||
menu = menus[1]
|
||||
self.assertEqual(menu['title'], "Second Link")
|
||||
|
||||
def test_do_make_menus_with_trailing_sep(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
{'type': 'sep'},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', return_value=True):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# ensure trailing sep was pruned
|
||||
menu = menus[0]
|
||||
self.assertEqual(len(menu['items']), 2)
|
||||
foo, bar = menu['items']
|
||||
self.assertEqual(foo['title'], 'Foo')
|
||||
self.assertEqual(bar['title'], 'Bar')
|
||||
|
||||
def test_do_make_menus_with_submenu(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{
|
||||
'title': "First Submenu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Second Submenu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Bar", 'url': '#'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
def is_allowed(request, item):
|
||||
if item.get('title') == 'Bar':
|
||||
return False
|
||||
return True
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', side_effect=is_allowed):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# first submenu remains, second is pruned
|
||||
menu = menus[0]
|
||||
self.assertEqual(len(menu['items']), 1)
|
||||
submenu = menu['items'][0]
|
||||
self.assertEqual(submenu['type'], 'submenu')
|
||||
self.assertEqual(submenu['title'], 'First Submenu')
|
||||
self.assertEqual(len(submenu['items']), 1)
|
||||
item = submenu['items'][0]
|
||||
self.assertEqual(item['title'], 'Foo')
|
Loading…
Reference in a new issue