From 8e65fc9575bde4cfef0f640ac3b3ff9ab532b8ce Mon Sep 17 00:00:00 2001 From: sdc50 Date: Mon, 6 Nov 2023 16:44:17 -0700 Subject: [PATCH] Fix Bokeh gizmo with Bokeh V3 --- environment.yml | 2 +- .../test_gizmo_options/test_bokeh_view.py | 10 ++++++++++ .../test_tethys_portal/test_settings.py | 5 +++++ tethys_gizmos/gizmo_options/bokeh_view.py | 4 +++- tethys_gizmos/gizmo_options/jobs_table.py | 2 +- tethys_portal/settings.py | 18 ++++++++++++------ 6 files changed, 32 insertions(+), 9 deletions(-) diff --git a/environment.yml b/environment.yml index 5d90d8448..aac7fea48 100644 --- a/environment.yml +++ b/environment.yml @@ -84,7 +84,7 @@ dependencies: # plotting Gizmo dependencies - plotly - - bokeh<3 + - bokeh # TethysJob Types - dask diff --git a/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_bokeh_view.py b/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_bokeh_view.py index 566e01e56..c43404707 100644 --- a/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_bokeh_view.py +++ b/tests/unit_tests/test_tethys_gizmos/test_gizmo_options/test_bokeh_view.py @@ -43,3 +43,13 @@ def test_get_bokeh_resources_server(self, mock_resources): files = bokeh_view.BokehView._get_bokeh_resources("js") for f in files: self.assertNotIn("/static", f) + + @mock.patch("tethys_gizmos.gizmo_options.bokeh_view.bokeh") + @mock.patch("tethys_gizmos.gizmo_options.bokeh_view.bk_settings") + def test_bokeh_resources_inline(self, mock_bk_settings, mock_bokeh): + mock_bokeh.__version__ = "3." + mock_bk_settings.resources.return_value = "inline" + bokeh_view.BokehView._bk_resources = None + bokeh_resources = bokeh_view.BokehView.bk_resources + self.assertEqual(bokeh_resources.mode, "server") + self.assertEqual(bokeh_resources.root_url, "/") diff --git a/tests/unit_tests/test_tethys_portal/test_settings.py b/tests/unit_tests/test_tethys_portal/test_settings.py index cd92dfda6..933b37d5b 100644 --- a/tests/unit_tests/test_tethys_portal/test_settings.py +++ b/tests/unit_tests/test_tethys_portal/test_settings.py @@ -253,3 +253,8 @@ def test_prefix_to_path_settings(self, _): self.assertEqual(settings.PREFIX_URL, "test") self.assertEqual(settings.STATIC_URL, "/test/static/") self.assertEqual(settings.LOGIN_URL, "/test/accounts/login/") + + @mock.patch("tethys_portal.optional_dependencies.has_module", return_value=True) + def test_bokeh_django_staticfiles_finder(self, _): + reload(settings) + self.assertIn("bokeh_django.static.BokehExtensionFinder", settings.STATICFILES_FINDERS) diff --git a/tethys_gizmos/gizmo_options/bokeh_view.py b/tethys_gizmos/gizmo_options/bokeh_view.py index 27f088481..79e8e8c41 100644 --- a/tethys_gizmos/gizmo_options/bokeh_view.py +++ b/tethys_gizmos/gizmo_options/bokeh_view.py @@ -7,6 +7,7 @@ from tethys_portal.optional_dependencies import optional_import # optional imports +bokeh = optional_import("bokeh") components = optional_import("components", from_module="bokeh.embed") Resources = optional_import("Resources", from_module="bokeh.resources") bk_settings = optional_import("settings", from_module="bokeh.settings") @@ -178,7 +179,6 @@ class BokehView(TethysGizmoOptions): select.line('date', 'close', source=source) select.ygrid.grid_line_color = None select.add_tools(range_tool) - select.toolbar.active_multi = range_tool time_series_plot = BokehView(column(time_series_fig, select)) @@ -254,6 +254,8 @@ def bk_resources(cls): # configure bokeh resources default = "server" if settings.STATICFILES_USE_NPM else "cdn" mode = bk_settings.resources(default=default) + if mode == "inline" and int(bokeh.__version__.split(".")[0]) > 2: + mode = "server" kwargs = {"mode": mode} if mode == "server": kwargs["root_url"] = "/" diff --git a/tethys_gizmos/gizmo_options/jobs_table.py b/tethys_gizmos/gizmo_options/jobs_table.py index 1142612e3..cdaba095e 100644 --- a/tethys_gizmos/gizmo_options/jobs_table.py +++ b/tethys_gizmos/gizmo_options/jobs_table.py @@ -184,7 +184,7 @@ class JobsTable(TethysGizmoOptions): refresh_interval(int): The refresh interval for the runtime and status fields in milliseconds. Default is 5000. show_detailed_status(bool): Show status of each node in CondorWorkflow jobs when True. Defaults to False. sort(bool|callable): Whether to sort the list of jobs in the table. If True, jobs are sorted by creation time from oldest (top of the table) to newest. If a callable is passed then it is used as the key to sort the jobs. Default is True. - reverse_sort(bool): Whether to reverse the sorting order. If ``sort`` is False then this argument has no effect. Default is False. + reverse_sort(bool): Whether to reverse the sorting order. If ``sort`` is False then this argument has no effect. Default is False. Controller Example diff --git a/tethys_portal/settings.py b/tethys_portal/settings.py index c13ecae55..af3b78e47 100644 --- a/tethys_portal/settings.py +++ b/tethys_portal/settings.py @@ -32,15 +32,15 @@ from django.contrib.messages import constants as message_constants from tethys_apps.utilities import relative_to_tethys_home -from tethys_portal.optional_dependencies import has_module from tethys_cli.cli_colors import write_warning from tethys_cli.gen_commands import generate_secret_key -from tethys_portal.optional_dependencies import optional_import +from tethys_portal.optional_dependencies import optional_import, has_module # optional imports bokeh_settings, bokehjsdir = optional_import( ("settings", "bokehjsdir"), from_module="bokeh.settings" ) +bokeh_django = optional_import("bokeh_django") log = logging.getLogger(__name__) this_module = sys.modules[__name__] @@ -60,8 +60,6 @@ log.exception( "There was an error while attempting to read the settings from the portal_config.yml file." ) -if has_module(bokeh_settings): - bokeh_settings.resources = portal_config_settings.pop("BOKEH_RESOURCES", "inline") # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = portal_config_settings.pop("SECRET_KEY", generate_secret_key()) @@ -416,14 +414,22 @@ if STATICFILES_USE_NPM: STATICFILES_DIRS.append(BASE_DIR / "static" / "node_modules") +if has_module(bokeh_settings): + bokeh_settings.resources = portal_config_settings.pop( + "BOKEH_RESOURCES", "server" if STATICFILES_USE_NPM else "cdn" + ) + STATICFILES_FINDERS = portal_config_settings.pop( "STATICFILES_FINDERS_OVERRIDE", - ( + [ "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", "tethys_apps.static_finders.TethysStaticFinder", - ), + ], ) +if has_module(bokeh_django): + STATICFILES_FINDERS.append("bokeh_django.static.BokehExtensionFinder") + STATICFILES_FINDERS = ( *STATICFILES_FINDERS, *portal_config_settings.pop("STATICFILES_FINDERS", []),