Rename insert-query to store-query

Also queries/insert to queries/store

Refs https://github.com/simonw/datasette/pull/2741#issuecomment-4549103663
This commit is contained in:
Simon Willison 2026-05-26 14:51:57 -07:00
commit 24887004cf
7 changed files with 47 additions and 40 deletions

View file

@ -54,9 +54,9 @@ from .views.database import (
QueryDeleteView,
QueryDefinitionView,
GlobalQueryListView,
QueryInsertView,
QueryListView,
QueryParametersView,
QueryStoreView,
QueryUpdateView,
)
from .views.index import IndexView
@ -2824,8 +2824,8 @@ class Datasette:
r"/(?P<database>[^\/\.]+)/-/queries/analyze$",
)
add_route(
QueryInsertView.as_view(self),
r"/(?P<database>[^\/\.]+)/-/queries/insert$",
QueryStoreView.as_view(self),
r"/(?P<database>[^\/\.]+)/-/queries/store$",
)
add_route(
ExecuteWriteAnalyzeView.as_view(self),

View file

@ -62,9 +62,9 @@ def register_actions():
resource_class=DatabaseResource,
),
Action(
name="insert-query",
abbr="iq",
description="Create saved queries",
name="store-query",
abbr="sq",
description="Create stored queries",
resource_class=DatabaseResource,
also_requires="execute-sql",
),

View file

@ -156,7 +156,7 @@ form.sql .query-create-sql textarea#sql-editor {
<h1 style="padding-left: 10px; border-left: 10px solid #{{ database_color }}">Create query</h1>
<form class="sql core query-create-form" action="{{ urls.database(database) }}/-/queries/insert" method="post" data-analyze-url="{{ urls.database(database) }}/-/queries/analyze">
<form class="sql core query-create-form" action="{{ urls.database(database) }}/-/queries/store" method="post" data-analyze-url="{{ urls.database(database) }}/-/queries/analyze">
<div class="query-create-fields">
<p class="query-create-field"><label for="query-title">Title</label> <input id="query-title" name="title" type="text" value="{{ title or "" }}"></p>
<p class="query-create-field"><label for="query-url-slug">URL</label> <span class="query-create-url-control"><span class="query-create-url-prefix">{{ urls.database(database) }}/</span><input id="query-url-slug" name="name" type="text" value="{{ name or "" }}"></span></p>

View file

@ -1419,7 +1419,7 @@ class QueryCreateView(BaseView):
actor=request.actor,
)
await self.ds.ensure_permission(
action="insert-query",
action="store-query",
resource=DatabaseResource(db.name),
actor=request.actor,
)
@ -1440,11 +1440,11 @@ class QueryCreateAnalyzeView(BaseView):
):
return _block_framing(_error(["Permission denied: need execute-sql"], 403))
if not await self.ds.allowed(
action="insert-query",
action="store-query",
resource=DatabaseResource(db.name),
actor=request.actor,
):
return _block_framing(_error(["Permission denied: need insert-query"], 403))
return _block_framing(_error(["Permission denied: need store-query"], 403))
invalid_keys = set(request.args) - {"sql"}
if invalid_keys:
@ -1462,8 +1462,8 @@ class QueryCreateAnalyzeView(BaseView):
)
class QueryInsertView(QueryCreateView):
name = "query-insert"
class QueryStoreView(QueryCreateView):
name = "query-store"
async def _error_response(self, request, db, query_data, message, status):
message = _query_create_form_error_message(message)
@ -1488,11 +1488,11 @@ class QueryInsertView(QueryCreateView):
):
return _error(["Permission denied: need execute-sql"], 403)
if not await self.ds.allowed(
action="insert-query",
action="store-query",
resource=DatabaseResource(db.name),
actor=request.actor,
):
return _error(["Permission denied: need insert-query"], 403)
return _error(["Permission denied: need store-query"], 403)
is_json = False
query_data = {}
@ -1961,8 +1961,8 @@ class QueryView(View):
resource=DatabaseResource(database=database),
actor=request.actor,
)
allow_insert_query = await datasette.allowed(
action="insert-query",
allow_store_query = await datasette.allowed(
action="store-query",
resource=DatabaseResource(database=database),
actor=request.actor,
)
@ -2020,13 +2020,13 @@ class QueryView(View):
if (
not canned_query
and allow_execute_sql
and allow_insert_query
and allow_store_query
and is_validated_sql
and ":_" not in sql
):
save_query_url = (
datasette.urls.database(database)
+ "/-/queries/insert?"
+ "/-/queries/store?"
+ urlencode({"sql": sql})
)

View file

@ -1293,11 +1293,12 @@ Actor is allowed to view a saved query page, e.g. https://latest.datasette.io/fi
``query`` is the name of the query (string)
.. _actions_insert_query:
.. _actions_store_query:
insert-query
------------
store-query
-----------
Actor is allowed to create saved queries in a database.
Actor is allowed to create stored queries in a database.
``resource`` - ``datasette.resources.DatabaseResource(database)``
``database`` is the name of the database (string)

View file

@ -518,14 +518,15 @@ Listing saved queries
Creating saved queries in the UI
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``GET /<database>/-/queries/-/create`` provides a form for creating saved queries.
``GET /<database>/-/queries/store`` provides a form for creating stored queries.
.. _QueryStoreView:
.. _QueryInsertView:
Creating saved queries
~~~~~~~~~~~~~~~~~~~~~~
``POST /<database>/-/queries/insert`` creates a saved query. This requires ``execute-sql`` and ``insert-query`` for the database.
``POST /<database>/-/queries/store`` creates a stored query. This requires ``execute-sql`` and ``store-query`` for the database.
.. _QueryParametersView:
.. _ExecuteWriteView:

View file

@ -470,7 +470,7 @@ async def test_query_actions_are_registered():
await ds.invoke_startup()
assert ds.get_action("execute-write-sql").resource_class is DatabaseResource
assert ds.get_action("insert-query").resource_class is DatabaseResource
assert ds.get_action("store-query").resource_class is DatabaseResource
assert ds.get_action("update-query").resource_class is QueryResource
assert ds.get_action("delete-query").resource_class is QueryResource
@ -537,15 +537,15 @@ async def test_analyze_write_query_rejects_writes_to_attached_databases():
@pytest.mark.asyncio
async def test_query_insert_api_creates_read_only_query():
async def test_query_store_api_creates_read_only_query():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
db = ds.add_memory_database("query_insert_api", name="data")
db = ds.add_memory_database("query_store_api", name="data")
await db.execute_write("create table dogs (id integer primary key, name text)")
await ds.invoke_startup()
response = await ds.client.post(
"/data/-/queries/insert",
"/data/-/queries/store",
actor={"id": "root"},
json={
"query": {
@ -860,7 +860,7 @@ async def test_global_query_list_api_and_html():
@pytest.mark.asyncio
async def test_query_insert_api_rejects_is_trusted():
async def test_query_store_api_rejects_is_trusted():
ds = Datasette(
memory=True,
default_deny=True,
@ -870,7 +870,7 @@ async def test_query_insert_api_rejects_is_trusted():
"permissions": {
"view-database": {"id": "writer"},
"execute-sql": {"id": "writer"},
"insert-query": {"id": "writer"},
"store-query": {"id": "writer"},
}
}
}
@ -880,7 +880,7 @@ async def test_query_insert_api_rejects_is_trusted():
await ds.invoke_startup()
response = await ds.client.post(
"/data/-/queries/insert",
"/data/-/queries/store",
actor={"id": "writer"},
json={"query": {"name": "trusted", "sql": "select 1", "is_trusted": True}},
)
@ -890,7 +890,7 @@ async def test_query_insert_api_rejects_is_trusted():
@pytest.mark.asyncio
async def test_query_insert_api_creates_writable_query():
async def test_query_store_api_creates_writable_query():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
db = ds.add_memory_database("query_write_api", name="data")
@ -898,7 +898,7 @@ async def test_query_insert_api_creates_writable_query():
await ds.invoke_startup()
response = await ds.client.post(
"/data/-/queries/insert",
"/data/-/queries/store",
actor={"id": "root"},
json={
"query": {
@ -962,14 +962,14 @@ async def test_query_update_and_delete_api():
@pytest.mark.asyncio
async def test_query_insert_api_rejects_magic_parameters():
async def test_query_store_api_rejects_magic_parameters():
ds = Datasette(memory=True, default_deny=True)
ds.root_enabled = True
ds.add_memory_database("query_magic_api", name="data")
await ds.invoke_startup()
response = await ds.client.post(
"/data/-/queries/insert",
"/data/-/queries/store",
actor={"id": "root"},
json={"query": {"name": "magic", "sql": "select :_actor_id"}},
)
@ -987,15 +987,19 @@ async def test_create_query_ui_and_arbitrary_sql_save_link():
await ds.invoke_startup()
create_response = await ds.client.get(
"/data/-/queries/insert?sql=select+*+from+dogs",
"/data/-/queries/store?sql=select+*+from+dogs",
actor={"id": "root"},
)
write_create_response = await ds.client.get(
"/data/-/queries/insert?sql=insert+into+dogs+(name)+values+('Cleo')",
"/data/-/queries/store?sql=insert+into+dogs+(name)+values+('Cleo')",
actor={"id": "root"},
)
blank_create_response = await ds.client.get(
"/data/-/queries/insert",
"/data/-/queries/store",
actor={"id": "root"},
)
old_insert_response = await ds.client.get(
"/data/-/queries/insert?sql=select+*+from+dogs",
actor={"id": "root"},
)
old_create_response = await ds.client.get(
@ -1075,7 +1079,8 @@ async def test_create_query_ui_and_arbitrary_sql_save_link():
)
assert query_response.status_code == 200
assert "Save this query" in query_response.text
assert "/data/-/queries/insert?sql=select+%2A+from+dogs" in query_response.text
assert "/data/-/queries/store?sql=select+%2A+from+dogs" in query_response.text
assert old_insert_response.status_code == 404
assert old_create_response.status_code == 404
@ -1153,7 +1158,7 @@ async def test_create_query_form_error_redisplays_form_with_values():
await ds.invoke_startup()
response = await ds.client.post(
"/data/-/queries/insert",
"/data/-/queries/store",
actor={"id": "root"},
data={
"name": "dogs",
@ -1176,7 +1181,7 @@ async def test_create_query_form_error_redisplays_form_with_values():
assert 'name="is_private" value="1" checked' in response.text
public_response = await ds.client.post(
"/data/-/queries/insert",
"/data/-/queries/store",
actor={"id": "root"},
data={
"name": "dogs",