kopia lustrzana https://github.com/simonw/datasette
view_actions plugin hook, closes #2297
rodzic
daf5ca02ca
commit
909c85cd2b
|
@ -145,6 +145,11 @@ def table_actions(datasette, actor, database, table, request):
|
||||||
"""Links for the table actions menu"""
|
"""Links for the table actions menu"""
|
||||||
|
|
||||||
|
|
||||||
|
@hookspec
|
||||||
|
def view_actions(datasette, actor, database, view, request):
|
||||||
|
"""Links for the view actions menu"""
|
||||||
|
|
||||||
|
|
||||||
@hookspec
|
@hookspec
|
||||||
def query_actions(datasette, actor, database, query_name, request, sql, params):
|
def query_actions(datasette, actor, database, query_name, request, sql, params):
|
||||||
"""Links for the query and canned query actions menu"""
|
"""Links for the query and canned query actions menu"""
|
||||||
|
|
|
@ -24,17 +24,17 @@
|
||||||
<div class="page-header" style="border-color: #{{ database_color }}">
|
<div class="page-header" style="border-color: #{{ database_color }}">
|
||||||
<h1>{{ metadata.get("title") or table }}{% if is_view %} (view){% endif %}{% if private %} 🔒{% endif %}</h1>
|
<h1>{{ metadata.get("title") or table }}{% if is_view %} (view){% endif %}{% if private %} 🔒{% endif %}</h1>
|
||||||
</div>
|
</div>
|
||||||
{% set links = table_actions() %}{% if links %}
|
{% set links = actions() %}{% if links %}
|
||||||
<div class="page-action-menu">
|
<div class="page-action-menu">
|
||||||
<details class="actions-menu-links details-menu">
|
<details class="actions-menu-links details-menu">
|
||||||
<summary>
|
<summary>
|
||||||
<div class="icon-text">
|
<div class="icon-text">
|
||||||
<svg class="icon" aria-labelledby="actions-menu-links-title" role="img" style="color: #fff" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="icon" aria-labelledby="actions-menu-links-title" role="img" style="color: #fff" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<title id="actions-menu-links-title">Table actions</title>
|
<title id="actions-menu-links-title">{% if is_view %}View{% else %}Table{% endif %} actions</title>
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Table actions</span>
|
<span>{% if is_view %}View{% else %}Table{% endif %} actions</span>
|
||||||
</div>
|
</div>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
|
|
|
@ -1401,22 +1401,28 @@ async def table_view_data(
|
||||||
"Primary keys for this table"
|
"Primary keys for this table"
|
||||||
return pks
|
return pks
|
||||||
|
|
||||||
async def extra_table_actions():
|
async def extra_actions():
|
||||||
async def table_actions():
|
async def actions():
|
||||||
links = []
|
links = []
|
||||||
for hook in pm.hook.table_actions(
|
kwargs = {
|
||||||
datasette=datasette,
|
"datasette": datasette,
|
||||||
table=table_name,
|
"database": database_name,
|
||||||
database=database_name,
|
"actor": request.actor,
|
||||||
actor=request.actor,
|
"request": request,
|
||||||
request=request,
|
}
|
||||||
):
|
if is_view:
|
||||||
|
kwargs["view"] = table_name
|
||||||
|
method = pm.hook.view_actions
|
||||||
|
else:
|
||||||
|
kwargs["table"] = table_name
|
||||||
|
method = pm.hook.table_actions
|
||||||
|
for hook in method(**kwargs):
|
||||||
extra_links = await await_me_maybe(hook)
|
extra_links = await await_me_maybe(hook)
|
||||||
if extra_links:
|
if extra_links:
|
||||||
links.extend(extra_links)
|
links.extend(extra_links)
|
||||||
return links
|
return links
|
||||||
|
|
||||||
return table_actions
|
return actions
|
||||||
|
|
||||||
async def extra_is_view():
|
async def extra_is_view():
|
||||||
return is_view
|
return is_view
|
||||||
|
@ -1606,7 +1612,7 @@ async def table_view_data(
|
||||||
"database",
|
"database",
|
||||||
"table",
|
"table",
|
||||||
"database_color",
|
"database_color",
|
||||||
"table_actions",
|
"actions",
|
||||||
"filters",
|
"filters",
|
||||||
"renderers",
|
"renderers",
|
||||||
"custom_table_templates",
|
"custom_table_templates",
|
||||||
|
@ -1647,7 +1653,7 @@ async def table_view_data(
|
||||||
extra_database,
|
extra_database,
|
||||||
extra_table,
|
extra_table,
|
||||||
extra_database_color,
|
extra_database_color,
|
||||||
extra_table_actions,
|
extra_actions,
|
||||||
extra_filters,
|
extra_filters,
|
||||||
extra_renderers,
|
extra_renderers,
|
||||||
extra_custom_table_templates,
|
extra_custom_table_templates,
|
||||||
|
|
|
@ -1521,6 +1521,28 @@ This example adds a new table action if the signed in user is ``"root"``:
|
||||||
|
|
||||||
Example: `datasette-graphql <https://datasette.io/plugins/datasette-graphql>`_
|
Example: `datasette-graphql <https://datasette.io/plugins/datasette-graphql>`_
|
||||||
|
|
||||||
|
.. _plugin_hook_view_actions:
|
||||||
|
|
||||||
|
view_actions(datasette, actor, database, view, 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.
|
||||||
|
|
||||||
|
``actor`` - dictionary or None
|
||||||
|
The currently authenticated :ref:`actor <authentication_actor>`.
|
||||||
|
|
||||||
|
``database`` - string
|
||||||
|
The name of the database.
|
||||||
|
|
||||||
|
``view`` - string
|
||||||
|
The name of the SQL view.
|
||||||
|
|
||||||
|
``request`` - :ref:`internals_request` or None
|
||||||
|
The current HTTP request. This can be ``None`` if the request object is not available.
|
||||||
|
|
||||||
|
Like :ref:`plugin_hook_table_actions` but for SQL views.
|
||||||
|
|
||||||
.. _plugin_hook_query_actions:
|
.. _plugin_hook_query_actions:
|
||||||
|
|
||||||
query_actions(datasette, actor, database, query_name, request, sql, params)
|
query_actions(datasette, actor, database, query_name, request, sql, params)
|
||||||
|
@ -1657,7 +1679,9 @@ This example adds a link an imagined tool for editing the homepage, only for sig
|
||||||
if actor:
|
if actor:
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"href": datasette.urls.path("/-/customize-homepage"),
|
"href": datasette.urls.path(
|
||||||
|
"/-/customize-homepage"
|
||||||
|
),
|
||||||
"label": "Customize homepage",
|
"label": "Customize homepage",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -56,6 +56,7 @@ EXPECTED_PLUGINS = [
|
||||||
"skip_csrf",
|
"skip_csrf",
|
||||||
"startup",
|
"startup",
|
||||||
"table_actions",
|
"table_actions",
|
||||||
|
"view_actions",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -391,6 +391,18 @@ def table_actions(datasette, database, table, actor):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def view_actions(datasette, database, view, actor):
|
||||||
|
if actor:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"href": datasette.urls.instance(),
|
||||||
|
"label": f"Database: {database}",
|
||||||
|
},
|
||||||
|
{"href": datasette.urls.instance(), "label": f"View: {view}"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def query_actions(datasette, database, query_name, sql):
|
def query_actions(datasette, database, query_name, sql):
|
||||||
# Don't explain an explain
|
# Don't explain an explain
|
||||||
|
|
|
@ -923,18 +923,34 @@ async def test_hook_menu_links(ds_client):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("table_or_view", ["facetable", "simple_view"])
|
async def test_hook_table_actions(ds_client):
|
||||||
async def test_hook_table_actions(ds_client, table_or_view):
|
response = await ds_client.get("/fixtures/facetable")
|
||||||
response = await ds_client.get(f"/fixtures/{table_or_view}")
|
|
||||||
assert get_actions_links(response.text) == []
|
assert get_actions_links(response.text) == []
|
||||||
|
|
||||||
response_2 = await ds_client.get(f"/fixtures/{table_or_view}?_bot=1&_hello=BOB")
|
response_2 = await ds_client.get("/fixtures/facetable?_bot=1&_hello=BOB")
|
||||||
assert sorted(
|
assert sorted(
|
||||||
get_actions_links(response_2.text), key=lambda link: link["label"]
|
get_actions_links(response_2.text), key=lambda link: link["label"]
|
||||||
) == [
|
) == [
|
||||||
{"label": "Database: fixtures", "href": "/", "description": None},
|
{"label": "Database: fixtures", "href": "/", "description": None},
|
||||||
{"label": "From async BOB", "href": "/", "description": None},
|
{"label": "From async BOB", "href": "/", "description": None},
|
||||||
{"label": f"Table: {table_or_view}", "href": "/", "description": None},
|
{"label": "Table: facetable", "href": "/", "description": None},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_hook_view_actions(ds_client):
|
||||||
|
response = await ds_client.get("/fixtures/simple_view")
|
||||||
|
assert get_actions_links(response.text) == []
|
||||||
|
|
||||||
|
response_2 = await ds_client.get(
|
||||||
|
"/fixtures/simple_view",
|
||||||
|
cookies={"ds_actor": ds_client.actor_cookie({"id": "bob"})},
|
||||||
|
)
|
||||||
|
assert sorted(
|
||||||
|
get_actions_links(response_2.text), key=lambda link: link["label"]
|
||||||
|
) == [
|
||||||
|
{"label": "Database: fixtures", "href": "/", "description": None},
|
||||||
|
{"label": "View: simple_view", "href": "/", "description": None},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue