mirror of
https://github.com/simonw/datasette.git
synced 2026-06-06 00:56:57 +02:00
parent
3b26b7aff0
commit
2d77e3334b
7 changed files with 96 additions and 38 deletions
|
|
@ -697,9 +697,9 @@ async def _prepare_query_update(datasette, request, db, existing, update):
|
|||
"on_error_redirect": update.get("on_error_redirect"),
|
||||
}
|
||||
update_kwargs = {}
|
||||
for field, value in field_values.items():
|
||||
if field in update:
|
||||
update_kwargs[field] = value
|
||||
for field_name, value in field_values.items():
|
||||
if field_name in update:
|
||||
update_kwargs[field_name] = value
|
||||
if parameters is not None:
|
||||
update_kwargs["parameters"] = parameters
|
||||
if "sql" in update:
|
||||
|
|
|
|||
|
|
@ -505,6 +505,48 @@ The JSON write API
|
|||
|
||||
Datasette provides a write API for JSON data. This is a POST-only API that requires an authenticated API token, see :ref:`CreateTokenView`. The token will need to have the specified :ref:`authentication_permissions`.
|
||||
|
||||
.. _QueryListView:
|
||||
|
||||
Listing saved queries
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``GET /<database>/-/queries`` returns saved query definitions the actor can view.
|
||||
|
||||
.. _QueryCreateView:
|
||||
|
||||
Creating saved queries in the UI
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``GET /<database>/-/queries/-/create`` provides a form for creating saved queries.
|
||||
|
||||
.. _QueryInsertView:
|
||||
|
||||
Creating saved queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``POST /<database>/-/queries/-/insert`` creates a saved query. This requires ``execute-sql`` and ``insert-query`` for the database.
|
||||
|
||||
.. _QueryDefinitionView:
|
||||
|
||||
Getting a saved query definition
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``GET /<database>/<query>/-/definition`` returns a saved query definition without executing it.
|
||||
|
||||
.. _QueryUpdateView:
|
||||
|
||||
Updating saved queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``POST /<database>/<query>/-/update`` updates a saved query using a JSON body with an ``"update"`` object.
|
||||
|
||||
.. _QueryDeleteView:
|
||||
|
||||
Deleting saved queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``POST /<database>/<query>/-/delete`` deletes a saved query.
|
||||
|
||||
.. _TableInsertView:
|
||||
|
||||
Inserting rows
|
||||
|
|
|
|||
|
|
@ -139,20 +139,6 @@ def startup(datasette):
|
|||
datasette._startup_catalog_databases = [
|
||||
row["database_name"] for row in catalog_rows
|
||||
]
|
||||
for database in datasette.databases:
|
||||
await datasette.add_query(
|
||||
database,
|
||||
"from_hook",
|
||||
"select 1, 'null' as actor_id",
|
||||
source="plugin",
|
||||
)
|
||||
result = await datasette.get_database(database).execute("select 1 + 1")
|
||||
await datasette.add_query(
|
||||
database,
|
||||
"from_async_hook",
|
||||
"select {}".format(result.first()[0]),
|
||||
source="plugin",
|
||||
)
|
||||
|
||||
return inner
|
||||
|
||||
|
|
|
|||
|
|
@ -254,10 +254,8 @@ def test_canned_query_permissions_on_database_page(canned_write_client):
|
|||
}
|
||||
assert query_names == {
|
||||
"add_name_specify_id_with_error_in_on_success_message_sql",
|
||||
"from_hook",
|
||||
"update_name",
|
||||
"add_name_specify_id",
|
||||
"from_async_hook",
|
||||
"canned_read",
|
||||
"add_name",
|
||||
}
|
||||
|
|
@ -284,8 +282,6 @@ def test_canned_query_permissions_on_database_page(canned_write_client):
|
|||
},
|
||||
{"name": "canned_read", "private": False},
|
||||
{"name": "delete_name", "private": True},
|
||||
{"name": "from_async_hook", "private": False},
|
||||
{"name": "from_hook", "private": False},
|
||||
{"name": "update_name", "private": False},
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -158,8 +158,6 @@ async def test_database_page(ds_client):
|
|||
queries_ul = soup.find("h2", string="Queries").find_next_sibling("ul")
|
||||
assert queries_ul is not None
|
||||
assert [
|
||||
("/fixtures/from_async_hook", "from_async_hook"),
|
||||
("/fixtures/from_hook", "from_hook"),
|
||||
("/fixtures/magic_parameters", "magic_parameters"),
|
||||
("/fixtures/neighborhood_search#fragment-goes-here", "Search neighborhoods"),
|
||||
("/fixtures/pragma_cache_size", "pragma_cache_size"),
|
||||
|
|
|
|||
|
|
@ -622,7 +622,6 @@ def test_padlocks_on_database_page(cascade_app_client):
|
|||
assert ">123_starts_with_digits</a></h3>" in response.text
|
||||
assert ">Table With Space In Name</a> 🔒</h3>" in response.text
|
||||
# Queries
|
||||
assert ">from_async_hook</a> 🔒</li>" in response.text
|
||||
assert ">query_two</a></li>" in response.text
|
||||
# Views
|
||||
assert ">paginated_view</a> 🔒</li>" in response.text
|
||||
|
|
|
|||
|
|
@ -885,24 +885,61 @@ async def test_hook_startup_catalog_populated(ds_client):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_plugin_startup_queries(ds_client):
|
||||
queries = (await ds_client.get("/fixtures.json")).json()["queries"]
|
||||
async def test_plugin_startup_can_add_queries():
|
||||
ds = Datasette(memory=True)
|
||||
ds.add_memory_database("plugin_startup_queries", name="data")
|
||||
|
||||
class AddQueriesPlugin:
|
||||
__name__ = "AddQueriesPlugin"
|
||||
|
||||
@hookimpl
|
||||
def startup(self, datasette):
|
||||
async def inner():
|
||||
result = await datasette.get_database("data").execute("select 1 + 1")
|
||||
await datasette.add_query(
|
||||
"data",
|
||||
"from_startup",
|
||||
"select {}".format(result.first()[0]),
|
||||
source="plugin",
|
||||
)
|
||||
|
||||
return inner
|
||||
|
||||
ds.pm.register(AddQueriesPlugin(), name="add_queries_plugin")
|
||||
try:
|
||||
response = await ds.client.get("/data.json")
|
||||
finally:
|
||||
ds.pm.unregister(name="add_queries_plugin")
|
||||
|
||||
queries = response.json()["queries"]
|
||||
queries_by_name = {q["name"]: q for q in queries}
|
||||
assert queries_by_name["from_async_hook"]["sql"] == "select 2"
|
||||
assert queries_by_name["from_async_hook"]["private"] is False
|
||||
assert queries_by_name["from_hook"]["sql"] == "select 1, 'null' as actor_id"
|
||||
assert queries_by_name["from_hook"]["private"] is False
|
||||
assert queries_by_name["from_startup"]["sql"] == "select 2"
|
||||
assert queries_by_name["from_startup"]["private"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_plugin_startup_query_from_hook(ds_client):
|
||||
response = await ds_client.get("/fixtures/from_hook.json?_shape=array")
|
||||
assert [{"1": 1, "actor_id": "null"}] == response.json()
|
||||
async def test_plugin_startup_query_can_execute():
|
||||
ds = Datasette(memory=True)
|
||||
ds.add_memory_database("plugin_startup_query_execute", name="data")
|
||||
|
||||
class AddQueryPlugin:
|
||||
__name__ = "AddQueryPlugin"
|
||||
|
||||
@hookimpl
|
||||
def startup(self, datasette):
|
||||
async def inner():
|
||||
await datasette.add_query(
|
||||
"data", "from_startup", "select 2", source="plugin"
|
||||
)
|
||||
|
||||
return inner
|
||||
|
||||
ds.pm.register(AddQueryPlugin(), name="add_query_plugin")
|
||||
try:
|
||||
response = await ds.client.get("/data/from_startup.json?_shape=array")
|
||||
finally:
|
||||
ds.pm.unregister(name="add_query_plugin")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_plugin_startup_query_from_async_hook(ds_client):
|
||||
response = await ds_client.get("/fixtures/from_async_hook.json?_shape=array")
|
||||
assert [{"2": 2}] == response.json()
|
||||
|
||||
|
||||
|
|
@ -1514,9 +1551,9 @@ async def test_hook_top_query(ds_client):
|
|||
async def test_hook_top_canned_query(ds_client):
|
||||
try:
|
||||
pm.register(SlotPlugin(), name="SlotPlugin")
|
||||
response = await ds_client.get("/fixtures/from_hook?z=xyz")
|
||||
response = await ds_client.get("/fixtures/magic_parameters?z=xyz")
|
||||
assert response.status_code == 200
|
||||
assert "Xtop_query:fixtures:from_hook:xyz" in response.text
|
||||
assert "Xtop_query:fixtures:magic_parameters:xyz" in response.text
|
||||
finally:
|
||||
pm.unregister(name="SlotPlugin")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue