diff --git a/datasette/app.py b/datasette/app.py index d85517e6..fc5b7d9d 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -833,7 +833,9 @@ class Datasette: async def menu_links(): links = [] for hook in pm.hook.menu_links( - datasette=self, actor=request.actor if request else None + datasette=self, + actor=request.actor if request else None, + request=request or None, ): extra_links = await await_me_maybe(hook) if extra_links: diff --git a/datasette/hookspecs.py b/datasette/hookspecs.py index 13a10680..579787a2 100644 --- a/datasette/hookspecs.py +++ b/datasette/hookspecs.py @@ -100,15 +100,15 @@ def forbidden(datasette, request, message): @hookspec -def menu_links(datasette, actor): +def menu_links(datasette, actor, request): """Links for the navigation menu""" @hookspec -def table_actions(datasette, actor, database, table): +def table_actions(datasette, actor, database, table, request): """Links for the table actions menu""" @hookspec -def database_actions(datasette, actor, database): +def database_actions(datasette, actor, database, request): """Links for the database actions menu""" diff --git a/datasette/views/database.py b/datasette/views/database.py index 58168ed7..53bdceed 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -110,6 +110,7 @@ class DatabaseView(DataView): datasette=self.ds, database=database, actor=request.actor, + request=request, ): extra_links = await await_me_maybe(hook) if extra_links: diff --git a/datasette/views/table.py b/datasette/views/table.py index b51d5e5e..81d4d721 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -894,6 +894,7 @@ class TableView(RowTableShared): table=table, database=database, actor=request.actor, + request=request, ): extra_links = await await_me_maybe(hook) if extra_links: diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst index 688eaa61..2c31e6f4 100644 --- a/docs/plugin_hooks.rst +++ b/docs/plugin_hooks.rst @@ -1015,8 +1015,8 @@ The function can alternatively return an awaitable function if it needs to make .. _plugin_hook_menu_links: -menu_links(datasette, actor) ----------------------------- +menu_links(datasette, actor, request) +------------------------------------- ``datasette`` - :ref:`internals_datasette` You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``, or to execute SQL queries. @@ -1024,6 +1024,9 @@ menu_links(datasette, actor) ``actor`` - dictionary or None The currently authenticated :ref:`actor `. +``request`` - object or None + The current HTTP :ref:`internals_request`. This can be ``None`` if the request object is not available. + This hook allows additional items to be included in the menu displayed by Datasette's top right menu icon. The hook should return a list of ``{"href": "...", "label": "..."}`` menu items. These will be added to the menu. @@ -1045,11 +1048,10 @@ This example adds a new menu item but only if the signed in user is ``"root"``: Using :ref:`internals_datasette_urls` here ensures that links in the menu will take the :ref:`setting_base_url` setting into account. - .. _plugin_hook_table_actions: -table_actions(datasette, actor, database, table) ------------------------------------------------- +table_actions(datasette, actor, database, table, request) +--------------------------------------------------------- ``datasette`` - :ref:`internals_datasette` You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``, or to execute SQL queries. @@ -1063,6 +1065,9 @@ table_actions(datasette, actor, database, table) ``table`` - string The name of the table. +``request`` - object + The current HTTP :ref:`internals_request`. This can be ``None`` if the request object is not available. + This hook allows table actions to be displayed in a menu accessed via an action icon at the top of the table page. It should return a list of ``{"href": "...", "label": "..."}`` menu items. It can alternatively return an ``async def`` awaitable function which returns a list of menu items. @@ -1083,8 +1088,8 @@ This example adds a new table action if the signed in user is ``"root"``: .. _plugin_hook_database_actions: -database_actions(datasette, actor, database) --------------------------------------------- +database_actions(datasette, actor, database, request) +----------------------------------------------------- ``datasette`` - :ref:`internals_datasette` You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``, or to execute SQL queries. @@ -1095,4 +1100,7 @@ database_actions(datasette, actor, database) ``database`` - string The name of the database. +``request`` - object + The current HTTP :ref:`internals_request`. + This hook is similar to :ref:`plugin_hook_table_actions` but populates an actions menu on the database page. diff --git a/tests/plugins/my_plugin.py b/tests/plugins/my_plugin.py index 26d06091..85a7467d 100644 --- a/tests/plugins/my_plugin.py +++ b/tests/plugins/my_plugin.py @@ -316,9 +316,12 @@ def forbidden(datasette, request, message): @hookimpl -def menu_links(datasette, actor): +def menu_links(datasette, actor, request): if actor: - return [{"href": datasette.urls.instance(), "label": "Hello"}] + label = "Hello" + if request.args.get("_hello"): + label += ", " + request.args["_hello"] + return [{"href": datasette.urls.instance(), "label": label}] @hookimpl @@ -334,11 +337,14 @@ def table_actions(datasette, database, table, actor): @hookimpl -def database_actions(datasette, database, actor): +def database_actions(datasette, database, actor, request): if actor: + label = f"Database: {database}" + if request.args.get("_hello"): + label += " - " + request.args["_hello"] return [ { "href": datasette.urls.instance(), - "label": f"Database: {database}", + "label": label, } ] diff --git a/tests/plugins/my_plugin_2.py b/tests/plugins/my_plugin_2.py index f3b794cf..b70372f3 100644 --- a/tests/plugins/my_plugin_2.py +++ b/tests/plugins/my_plugin_2.py @@ -158,9 +158,12 @@ def menu_links(datasette, actor): @hookimpl -def table_actions(datasette, database, table, actor): +def table_actions(datasette, database, table, actor, request): async def inner(): if actor: - return [{"href": datasette.urls.instance(), "label": "From async"}] + label = "From async" + if request.args.get("_hello"): + label += " " + request.args["_hello"] + return [{"href": datasette.urls.instance(), "label": label}] return inner diff --git a/tests/test_plugins.py b/tests/test_plugins.py index ee6f1efa..b3561dd5 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -781,9 +781,9 @@ def test_hook_menu_links(app_client): response = app_client.get("/") assert get_menu_links(response.text) == [] - response_2 = app_client.get("/?_bot=1") + response_2 = app_client.get("/?_bot=1&_hello=BOB") assert get_menu_links(response_2.text) == [ - {"label": "Hello", "href": "/"}, + {"label": "Hello, BOB", "href": "/"}, {"label": "Hello 2", "href": "/"}, ] @@ -800,12 +800,12 @@ def test_hook_table_actions(app_client, table_or_view): response = app_client.get(f"/fixtures/{table_or_view}") assert get_table_actions_links(response.text) == [] - response_2 = app_client.get(f"/fixtures/{table_or_view}?_bot=1") + response_2 = app_client.get(f"/fixtures/{table_or_view}?_bot=1&_hello=BOB") assert sorted( get_table_actions_links(response_2.text), key=lambda l: l["label"] ) == [ {"label": "Database: fixtures", "href": "/"}, - {"label": "From async", "href": "/"}, + {"label": "From async BOB", "href": "/"}, {"label": f"Table: {table_or_view}", "href": "/"}, ] @@ -821,7 +821,7 @@ def test_hook_database_actions(app_client): response = app_client.get("/fixtures") assert get_table_actions_links(response.text) == [] - response_2 = app_client.get("/fixtures?_bot=1") + response_2 = app_client.get("/fixtures?_bot=1&_hello=BOB") assert get_table_actions_links(response_2.text) == [ - {"label": "Database: fixtures", "href": "/"}, + {"label": "Database: fixtures - BOB", "href": "/"}, ]