kopia lustrzana https://github.com/simonw/datasette
database_actions() plugin hook, closes #1077
rodzic
b61f6cceb5
commit
7b19492070
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -38,6 +38,7 @@ EXPECTED_PLUGINS = [
|
|||
"actor_from_request",
|
||||
"asgi_wrapper",
|
||||
"canned_queries",
|
||||
"database_actions",
|
||||
"extra_body_script",
|
||||
"extra_css_urls",
|
||||
"extra_js_urls",
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -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": "/"},
|
||||
]
|
||||
|
|
Ładowanie…
Reference in New Issue