From ee62bf9bdcb1d1cd3e6b2d846d4aeb698af2b711 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sat, 25 Oct 2025 18:02:26 -0700 Subject: [PATCH] Fix minor irritation with /-/allowed UI --- datasette/templates/debug_allowed.html | 11 ++++--- datasette/views/special.py | 41 ++++++++++++-------------- tests/test_permission_endpoints.py | 8 +++-- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/datasette/templates/debug_allowed.html b/datasette/templates/debug_allowed.html index 9483926b..b06e1faf 100644 --- a/datasette/templates/debug_allowed.html +++ b/datasette/templates/debug_allowed.html @@ -42,7 +42,7 @@
- Filter results to a specific child resource (requires parent) + Filter results to a specific child resource (requires parent to be set)
@@ -236,12 +236,15 @@ function displayError(data) { const parentInput = document.getElementById('parent'); const childInput = document.getElementById('child'); -childInput.addEventListener('focus', () => { +parentInput.addEventListener('input', () => { + childInput.disabled = !parentInput.value; if (!parentInput.value) { - alert('Please specify a parent resource first before filtering by child resource.'); - parentInput.focus(); + childInput.value = ''; } }); + +// Initialize disabled state +childInput.disabled = !parentInput.value; {% endblock %} diff --git a/datasette/views/special.py b/datasette/views/special.py index ca155a04..e896becc 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -189,22 +189,6 @@ class AllowedResourcesView(BaseView): name = "allowed" has_json_alternate = False - CANDIDATE_SQL = { - "view-table": ( - "SELECT database_name AS parent, table_name AS child FROM catalog_tables", - {}, - ), - "view-database": ( - "SELECT database_name AS parent, NULL AS child FROM catalog_databases", - {}, - ), - "view-instance": ("SELECT NULL AS parent, NULL AS child", {}), - "execute-sql": ( - "SELECT database_name AS parent, NULL AS child FROM catalog_databases", - {}, - ), - } - async def get(self, request): await self.ds.refresh_schemas() @@ -218,11 +202,29 @@ class AllowedResourcesView(BaseView): if not as_format: # Render the HTML form (even if query parameters are present) + # Put most common/interesting actions first + priority_actions = [ + "view-instance", + "view-database", + "view-table", + "view-query", + "execute-sql", + "insert-row", + "update-row", + "delete-row", + ] + actions = list(self.ds.actions.keys()) + # Priority actions first (in order), then remaining alphabetically + sorted_actions = [a for a in priority_actions if a in actions] + sorted_actions.extend( + sorted(a for a in actions if a not in priority_actions) + ) + return await self.render( ["debug_allowed.html"], request, { - "supported_actions": sorted(self.CANDIDATE_SQL.keys()), + "supported_actions": sorted_actions, }, ) @@ -232,11 +234,6 @@ class AllowedResourcesView(BaseView): return Response.json({"error": "action parameter is required"}, status=400) if action not in self.ds.actions: return Response.json({"error": f"Unknown action: {action}"}, status=404) - if action not in self.CANDIDATE_SQL: - return Response.json( - {"error": f"Action '{action}' is not supported by this endpoint"}, - status=400, - ) actor = request.actor if isinstance(request.actor, dict) else None actor_id = actor.get("id") if actor else None diff --git a/tests/test_permission_endpoints.py b/tests/test_permission_endpoints.py index a1aa4578..65280a06 100644 --- a/tests/test_permission_endpoints.py +++ b/tests/test_permission_endpoints.py @@ -69,8 +69,12 @@ async def ds_with_permissions(): ("/-/allowed.json", 400, {"error"}), # Invalid action ("/-/allowed.json?action=nonexistent", 404, {"error"}), - # Unsupported action (valid but not in CANDIDATE_SQL) - ("/-/allowed.json?action=insert-row", 400, {"error"}), + # Any valid action works, even if no permission rules exist for it + ( + "/-/allowed.json?action=insert-row", + 200, + {"action", "items", "total", "page"}, + ), ], ) async def test_allowed_json_basic(