database_actions() plugin hook, closes #1077

pull/1085/head
Simon Willison 2020-11-02 10:27:25 -08:00
rodzic b61f6cceb5
commit 7b19492070
10 zmienionych plików z 97 dodań i 8 usunięć

Wyświetl plik

@ -107,3 +107,8 @@ def menu_links(datasette, actor):
@hookspec
def table_actions(datasette, actor, database, table):
"Links for the table actions menu"
@hookspec
def database_actions(datasette, actor, database):
"Links for the database actions menu"

Wyświetl plik

@ -360,11 +360,11 @@ details .nav-menu-inner {
display: block;
}
/* Table actions menu */
.table-menu-links {
/* Table/database actions menu */
.actions-menu-links {
position: relative;
}
.table-menu-links .dropdown-menu {
.actions-menu-links .dropdown-menu {
position: absolute;
top: 2rem;
right: 0;

Wyświetl plik

@ -18,7 +18,30 @@
{% block content %}
<h1 style="padding-left: 10px; border-left: 10px solid #{{ database_color(database) }}">{{ metadata.title or database }}{% if private %} 🔒{% endif %}</h1>
<div class="page-header" style="border-color: #{{ database_color(database) }}">
<h1>{{ metadata.title or database }}{% if private %} 🔒{% endif %}</h1>
{% set links = database_actions() %}{% if links %}
<details class="actions-menu-links">
<summary><svg aria-labelledby="actions-menu-links-title" role="img"
style="color: #666" xmlns="http://www.w3.org/2000/svg"
width="28" height="28" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<title id="actions-menu-links-title">Table actions</title>
<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>
</svg></summary>
<div class="dropdown-menu">
{% if links %}
<ul>
{% for link in links %}
<li><a href="{{ link.href }}">{{ link.label }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
</details>{% endif %}
</div>
{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}

Wyświetl plik

@ -28,12 +28,12 @@
<div class="page-header" style="border-color: #{{ database_color(database) }}">
<h1>{{ metadata.title or table }}{% if is_view %} (view){% endif %}{% if private %} 🔒{% endif %}</h1>
{% set links = table_actions() %}{% if links %}
<details class="table-menu-links">
<summary><svg aria-labelledby="table-menu-links-title" role="img"
<details class="actions-menu-links">
<summary><svg aria-labelledby="actions-menu-links-title" role="img"
style="color: #666" xmlns="http://www.w3.org/2000/svg"
width="28" height="28" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<title id="table-menu-links-title">Table actions</title>
<title id="actions-menu-links-title">Table actions</title>
<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>
</svg></summary>

Wyświetl plik

@ -6,6 +6,7 @@ import json
from urllib.parse import parse_qsl, urlencode
from datasette.utils import (
await_me_maybe,
check_visibility,
to_css_class,
validate_sql_select,
@ -101,6 +102,19 @@ class DatabaseView(DataView):
)
if visible:
canned_queries.append(dict(query, private=private))
async def database_actions():
links = []
for hook in pm.hook.database_actions(
datasette=self.ds,
database=database,
actor=request.actor,
):
extra_links = await await_me_maybe(hook)
if extra_links:
links.extend(extra_links)
return links
return (
{
"database": database,
@ -118,6 +132,7 @@ class DatabaseView(DataView):
),
},
{
"database_actions": database_actions,
"show_hidden": request.args.get("_show_hidden"),
"editable": True,
"metadata": metadata,

Wyświetl plik

@ -1057,3 +1057,19 @@ This example adds a new table action if the signed in user is ``"root"``:
"href": datasette.urls.path("/-/edit-schema/{}/{}".format(database, table)),
"label": "Edit schema for this table",
}]
.. _plugin_hook_database_actions:
database_actions(datasette, actor, database)
--------------------------------------------
``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.
This hook is similar to :ref:`plugin_hook_table_actions` but populates an actions menu on the database page.

Wyświetl plik

@ -38,6 +38,7 @@ EXPECTED_PLUGINS = [
"actor_from_request",
"asgi_wrapper",
"canned_queries",
"database_actions",
"extra_body_script",
"extra_css_urls",
"extra_js_urls",

Wyświetl plik

@ -333,3 +333,14 @@ def table_actions(datasette, database, table, actor):
},
{"href": datasette.urls.instance(), "label": "Table: {}".format(table)},
]
@hookimpl
def database_actions(datasette, database, actor):
if actor:
return [
{
"href": datasette.urls.instance(),
"label": "Database: {}".format(database),
}
]

Wyświetl plik

@ -60,6 +60,7 @@ def test_homepage_sort_by_relationships(app_client):
def test_database_page(app_client):
response = app_client.get("/fixtures.json")
assert response.status == 200
data = response.json
assert "fixtures" == data["database"]
assert [

Wyświetl plik

@ -788,7 +788,7 @@ def test_hook_menu_links(app_client):
def test_hook_table_actions(app_client, table_or_view):
def get_table_actions_links(html):
soup = Soup(html, "html.parser")
details = soup.find("details", {"class": "table-menu-links"})
details = soup.find("details", {"class": "actions-menu-links"})
if details is None:
return []
return [{"label": a.text, "href": a["href"]} for a in details.select("a")]
@ -802,3 +802,20 @@ def test_hook_table_actions(app_client, table_or_view):
{"label": "Database: fixtures", "href": "/"},
{"label": "Table: {}".format(table_or_view), "href": "/"},
]
def test_hook_database_actions(app_client):
def get_table_actions_links(html):
soup = Soup(html, "html.parser")
details = soup.find("details", {"class": "actions-menu-links"})
if details is None:
return []
return [{"label": a.text, "href": a["href"]} for a in details.select("a")]
response = app_client.get("/fixtures")
assert get_table_actions_links(response.text) == []
response_2 = app_client.get("/fixtures?_bot=1")
assert get_table_actions_links(response_2.text) == [
{"label": "Database: fixtures", "href": "/"},
]