3
0
Fork 0

fix: expose setting to choose menu handler, in appinfo/configure

This commit is contained in:
Lance Edgar 2025-01-13 12:55:34 -06:00
parent 8ba44e10bd
commit 2b3d69a379
5 changed files with 181 additions and 12 deletions

View file

@ -70,6 +70,9 @@ wuttaweb = "wuttaweb.conf:WuttaWebConfigExtension"
[project.entry-points."wutta.typer_imports"]
wuttaweb = "wuttaweb.cli"
[project.entry-points."wutta.web.menus"]
wuttaweb = "wuttaweb.menus:MenuHandler"
[project.urls]
Homepage = "https://wuttaproject.org/"

View file

@ -24,7 +24,10 @@
Web Handler
"""
import warnings
from wuttjamaican.app import GenericHandler
from wuttjamaican.util import load_entry_points
from wuttaweb import static, forms, grids
@ -106,22 +109,87 @@ class WebHandler(GenericHandler):
def get_menu_handler(self, **kwargs):
"""
Get the configured "menu" handler for the web app.
Get the configured :term:`menu handler` for the web app.
Specify a custom handler in your config file like this:
.. code-block:: ini
[wutta.web]
menus.handler_spec = poser.web.menus:PoserMenuHandler
menus.handler.spec = poser.web.menus:PoserMenuHandler
:returns: Instance of :class:`~wuttaweb.menus.MenuHandler`.
"""
if not hasattr(self, 'menu_handler'):
spec = self.config.get(f'{self.appname}.web.menus.handler_spec',
spec = self.config.get(f'{self.appname}.web.menus.handler.spec')
if not spec:
spec = self.config.get(f'{self.appname}.web.menus.handler_spec')
if spec:
warnings.warn(f"setting '{self.appname}.web.menus.handler_spec' is deprecated; "
f"please use '{self.appname}.web.menus.handler_spec' instead",
DeprecationWarning)
else:
spec = self.config.get(f'{self.appname}.web.menus.handler.default_spec',
default='wuttaweb.menus:MenuHandler')
self.menu_handler = self.app.load_object(spec)(self.config)
return self.menu_handler
factory = self.app.load_object(spec)
return factory(self.config)
def get_menu_handler_specs(self, default=None):
"""
Get the :term:`spec` strings for all available :term:`menu
handlers <menu handler>`. See also
:meth:`get_menu_handler()`.
:param default: Default spec string(s) to include, even if not
registered. Can be a string or list of strings.
:returns: List of menu handler spec strings.
This will gather available spec strings from the following:
First, the ``default`` as provided by caller.
Second, the default spec from config, if set; for example:
.. code-block:: ini
[wutta.web]
menus.handler.default_spec = poser.web.menus:PoserMenuHandler
Third, each spec registered via entry points. For instance in
``pyproject.toml``:
.. code-block:: toml
[project.entry-points."wutta.web.menus"]
poser = "poser.web.menus:PoserMenuHandler"
The final list will be "sorted" according to the above, with
the latter registered handlers being sorted alphabetically.
"""
handlers = []
# defaults from caller
if isinstance(default, str):
handlers.append(default)
elif default:
handlers.extend(default)
# configured default, if applicable
default = self.config.get(f'{self.config.appname}.web.menus.handler.default_spec')
if default and default not in handlers:
handlers.append(default)
# registered via entry points
registered = []
for Handler in load_entry_points(f'{self.appname}.web.menus').values():
spec = Handler.get_spec()
if spec not in handlers:
registered.append(spec)
if registered:
registered.sort()
handlers.extend(registered)
return handlers
def make_form(self, request, **kwargs):
"""

View file

@ -41,6 +41,21 @@
</b-checkbox>
</b-field>
<b-field label="Menu Handler">
<input type="hidden"
name="${app.appname}.web.menus.handler.spec"
:value="simpleSettings['${app.appname}.web.menus.handler.spec']" />
<b-select v-model="simpleSettings['${app.appname}.web.menus.handler.spec']"
@input="settingsNeedSaved = true">
<option :value="null">(use default)</option>
<option v-for="handler in menuHandlers"
:key="handler.spec"
:value="handler.spec">
{{ handler.spec }}
</option>
</b-select>
</b-field>
</div>
<h3 class="block is-size-3">User/Auth</h3>
@ -241,6 +256,8 @@
${parent.modify_vue_vars()}
<script>
ThisPageData.menuHandlers = ${json.dumps(menu_handlers)|n}
ThisPageData.weblibs = ${json.dumps(weblibs or [])|n}
ThisPageData.editWebLibraryShowDialog = false

View file

@ -129,6 +129,10 @@ class AppInfoView(MasterView):
{'name': f'{self.config.appname}.node_title'},
{'name': f'{self.config.appname}.production',
'type': bool},
{'name': f'{self.config.appname}.web.menus.handler.spec'},
# nb. this is deprecated; we define so it is auto-deleted
# when we replace with newer setting
{'name': f'{self.config.appname}.web.menus.handler_spec'},
# user/auth
{'name': 'wuttaweb.home_redirect_to_login',
@ -164,11 +168,15 @@ class AppInfoView(MasterView):
def configure_get_context(self, **kwargs):
""" """
# normal context
context = super().configure_get_context(**kwargs)
# we will add `weblibs` to context, based on config values
# add registered menu handlers
web = self.app.get_web_handler()
handlers = web.get_menu_handler_specs()
handlers = [{'spec': spec} for spec in handlers]
context['menu_handlers'] = handlers
# add `weblibs` to context, based on config values
weblibs = self.get_weblibs()
for key in weblibs:
title = weblibs[key]
@ -192,8 +200,8 @@ class AppInfoView(MasterView):
'live_url': get_liburl(self.request, key,
prefix=self.weblib_config_prefix),
}
context['weblibs'] = list(weblibs.values())
return context

View file

@ -1,5 +1,7 @@
# -*- coding: utf-8; -*-
from unittest.mock import patch
from wuttaweb import handler as mod, static
from wuttaweb.forms import Form
from wuttaweb.grids import Grid
@ -7,6 +9,16 @@ from wuttaweb.menus import MenuHandler
from wuttaweb.testing import WebTestCase
class MockMenuHandler(MenuHandler):
pass
class LegacyMenuHandler(MenuHandler):
pass
class AnotherMenuHandler(MenuHandler):
pass
class TestWebHandler(WebTestCase):
def make_handler(self):
@ -60,10 +72,71 @@ class TestWebHandler(WebTestCase):
url = handler.get_main_logo_url(self.request)
self.assertEqual(url, '/testing/other.png')
def test_menu_handler_default(self):
def test_get_menu_handler(self):
handler = self.make_handler()
# built-in default
menus = handler.get_menu_handler()
self.assertIsInstance(menus, MenuHandler)
self.assertIs(type(menus), MenuHandler)
# configured default
self.config.setdefault('wutta.web.menus.handler.default_spec',
'tests.test_handler:MockMenuHandler')
menus = handler.get_menu_handler()
self.assertIsInstance(menus, MockMenuHandler)
# configured handler (legacy)
self.config.setdefault('wutta.web.menus.handler_spec',
'tests.test_handler:LegacyMenuHandler')
menus = handler.get_menu_handler()
self.assertIsInstance(menus, LegacyMenuHandler)
# configued handler (proper)
self.config.setdefault('wutta.web.menus.handler.spec',
'tests.test_handler:AnotherMenuHandler')
menus = handler.get_menu_handler()
self.assertIsInstance(menus, AnotherMenuHandler)
def test_get_menu_handler_specs(self):
handler = self.make_handler()
# at least one spec by default
specs = handler.get_menu_handler_specs()
self.assertIn('wuttaweb.menus:MenuHandler', specs)
# caller can specify default as string
specs = handler.get_menu_handler_specs(default='tests.test_handler:MockMenuHandler')
self.assertIn('wuttaweb.menus:MenuHandler', specs)
self.assertIn('tests.test_handler:MockMenuHandler', specs)
self.assertNotIn('tests.test_handler:AnotherMenuHandler', specs)
# caller can specify default as list
specs = handler.get_menu_handler_specs(default=[
'tests.test_handler:MockMenuHandler',
'tests.test_handler:AnotherMenuHandler'])
self.assertIn('wuttaweb.menus:MenuHandler', specs)
self.assertIn('tests.test_handler:MockMenuHandler', specs)
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
# default can be configured
self.config.setdefault('wutta.web.menus.handler.default_spec',
'tests.test_handler:AnotherMenuHandler')
specs = handler.get_menu_handler_specs()
self.assertIn('wuttaweb.menus:MenuHandler', specs)
self.assertNotIn('tests.test_handler:MockMenuHandler', specs)
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
# the rest come from entry points
with patch.object(mod, 'load_entry_points', return_value={
'legacy': LegacyMenuHandler,
}):
specs = handler.get_menu_handler_specs()
self.assertNotIn('wuttaweb.menus:MenuHandler', specs)
self.assertNotIn('tests.test_handler:MockMenuHandler', specs)
self.assertIn('tests.test_handler:LegacyMenuHandler', specs)
# nb. this remains from previous config default
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
def test_make_form(self):
handler = self.make_handler()