fix: expose setting to choose menu handler, in appinfo/configure
This commit is contained in:
		
							parent
							
								
									8ba44e10bd
								
							
						
					
					
						commit
						2b3d69a379
					
				
					 5 changed files with 181 additions and 12 deletions
				
			
		|  | @ -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/" | ||||
|  |  | |||
|  | @ -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', | ||||
|                                    default='wuttaweb.menus:MenuHandler') | ||||
|             self.menu_handler = self.app.load_object(spec)(self.config) | ||||
|         return self.menu_handler | ||||
|         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') | ||||
|         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): | ||||
|         """ | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue