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"] [project.entry-points."wutta.typer_imports"]
wuttaweb = "wuttaweb.cli" wuttaweb = "wuttaweb.cli"
[project.entry-points."wutta.web.menus"]
wuttaweb = "wuttaweb.menus:MenuHandler"
[project.urls] [project.urls]
Homepage = "https://wuttaproject.org/" Homepage = "https://wuttaproject.org/"

View file

@ -24,7 +24,10 @@
Web Handler Web Handler
""" """
import warnings
from wuttjamaican.app import GenericHandler from wuttjamaican.app import GenericHandler
from wuttjamaican.util import load_entry_points
from wuttaweb import static, forms, grids from wuttaweb import static, forms, grids
@ -106,22 +109,87 @@ class WebHandler(GenericHandler):
def get_menu_handler(self, **kwargs): 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: Specify a custom handler in your config file like this:
.. code-block:: ini .. code-block:: ini
[wutta.web] [wutta.web]
menus.handler_spec = poser.web.menus:PoserMenuHandler menus.handler.spec = poser.web.menus:PoserMenuHandler
:returns: Instance of :class:`~wuttaweb.menus.MenuHandler`. :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') default='wuttaweb.menus:MenuHandler')
self.menu_handler = self.app.load_object(spec)(self.config) factory = self.app.load_object(spec)
return self.menu_handler 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): def make_form(self, request, **kwargs):
""" """

View file

@ -41,6 +41,21 @@
</b-checkbox> </b-checkbox>
</b-field> </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> </div>
<h3 class="block is-size-3">User/Auth</h3> <h3 class="block is-size-3">User/Auth</h3>
@ -241,6 +256,8 @@
${parent.modify_vue_vars()} ${parent.modify_vue_vars()}
<script> <script>
ThisPageData.menuHandlers = ${json.dumps(menu_handlers)|n}
ThisPageData.weblibs = ${json.dumps(weblibs or [])|n} ThisPageData.weblibs = ${json.dumps(weblibs or [])|n}
ThisPageData.editWebLibraryShowDialog = false ThisPageData.editWebLibraryShowDialog = false

View file

@ -129,6 +129,10 @@ class AppInfoView(MasterView):
{'name': f'{self.config.appname}.node_title'}, {'name': f'{self.config.appname}.node_title'},
{'name': f'{self.config.appname}.production', {'name': f'{self.config.appname}.production',
'type': bool}, '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 # user/auth
{'name': 'wuttaweb.home_redirect_to_login', {'name': 'wuttaweb.home_redirect_to_login',
@ -164,11 +168,15 @@ class AppInfoView(MasterView):
def configure_get_context(self, **kwargs): def configure_get_context(self, **kwargs):
""" """ """ """
# normal context
context = super().configure_get_context(**kwargs) 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() weblibs = self.get_weblibs()
for key in weblibs: for key in weblibs:
title = weblibs[key] title = weblibs[key]
@ -192,8 +200,8 @@ class AppInfoView(MasterView):
'live_url': get_liburl(self.request, key, 'live_url': get_liburl(self.request, key,
prefix=self.weblib_config_prefix), prefix=self.weblib_config_prefix),
} }
context['weblibs'] = list(weblibs.values()) context['weblibs'] = list(weblibs.values())
return context return context

View file

@ -1,5 +1,7 @@
# -*- coding: utf-8; -*- # -*- coding: utf-8; -*-
from unittest.mock import patch
from wuttaweb import handler as mod, static from wuttaweb import handler as mod, static
from wuttaweb.forms import Form from wuttaweb.forms import Form
from wuttaweb.grids import Grid from wuttaweb.grids import Grid
@ -7,6 +9,16 @@ from wuttaweb.menus import MenuHandler
from wuttaweb.testing import WebTestCase from wuttaweb.testing import WebTestCase
class MockMenuHandler(MenuHandler):
pass
class LegacyMenuHandler(MenuHandler):
pass
class AnotherMenuHandler(MenuHandler):
pass
class TestWebHandler(WebTestCase): class TestWebHandler(WebTestCase):
def make_handler(self): def make_handler(self):
@ -60,10 +72,71 @@ class TestWebHandler(WebTestCase):
url = handler.get_main_logo_url(self.request) url = handler.get_main_logo_url(self.request)
self.assertEqual(url, '/testing/other.png') self.assertEqual(url, '/testing/other.png')
def test_menu_handler_default(self): def test_get_menu_handler(self):
handler = self.make_handler() handler = self.make_handler()
# built-in default
menus = handler.get_menu_handler() menus = handler.get_menu_handler()
self.assertIsInstance(menus, MenuHandler) 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): def test_make_form(self):
handler = self.make_handler() handler = self.make_handler()