mirror of
https://github.com/simonw/datasette.git
synced 2026-05-27 12:34:37 +02:00
parent
e0d39ba69f
commit
e62a5ea337
8 changed files with 63 additions and 63 deletions
|
|
@ -615,7 +615,7 @@ class Datasette:
|
|||
fragment=query_config.get("fragment"),
|
||||
parameters=query_config.get("params"),
|
||||
is_write=bool(query_config.get("write")),
|
||||
published=bool(query_config.get("published")),
|
||||
is_published=bool(query_config.get("is_published")),
|
||||
source="config",
|
||||
on_success_message=query_config.get("on_success_message"),
|
||||
on_success_message_sql=query_config.get("on_success_message_sql"),
|
||||
|
|
@ -1081,7 +1081,7 @@ class Datasette:
|
|||
"parameters": parameters,
|
||||
"is_write": is_write,
|
||||
"write": is_write,
|
||||
"published": bool(row["published"]),
|
||||
"is_published": bool(row["is_published"]),
|
||||
"source": row["source"],
|
||||
"owner_id": row["owner_id"],
|
||||
"on_success_message": options.get("on_success_message"),
|
||||
|
|
@ -1116,7 +1116,7 @@ class Datasette:
|
|||
fragment=None,
|
||||
parameters=None,
|
||||
is_write=False,
|
||||
published=False,
|
||||
is_published=False,
|
||||
source="plugin",
|
||||
owner_id=None,
|
||||
on_success_message=None,
|
||||
|
|
@ -1141,7 +1141,7 @@ class Datasette:
|
|||
sql_statement = """
|
||||
INSERT INTO queries (
|
||||
database_name, name, sql, title, description, description_html,
|
||||
options, parameters, is_write, published, source, owner_id
|
||||
options, parameters, is_write, is_published, source, owner_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
if replace:
|
||||
|
|
@ -1154,7 +1154,7 @@ class Datasette:
|
|||
options = excluded.options,
|
||||
parameters = excluded.parameters,
|
||||
is_write = excluded.is_write,
|
||||
published = excluded.published,
|
||||
is_published = excluded.is_published,
|
||||
source = excluded.source,
|
||||
owner_id = excluded.owner_id,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
|
|
@ -1171,7 +1171,7 @@ class Datasette:
|
|||
options_json,
|
||||
parameters_json,
|
||||
int(bool(is_write)),
|
||||
int(bool(published)),
|
||||
int(bool(is_published)),
|
||||
source,
|
||||
owner_id,
|
||||
],
|
||||
|
|
@ -1190,7 +1190,7 @@ class Datasette:
|
|||
fragment=UNCHANGED,
|
||||
parameters=UNCHANGED,
|
||||
is_write=UNCHANGED,
|
||||
published=UNCHANGED,
|
||||
is_published=UNCHANGED,
|
||||
source=UNCHANGED,
|
||||
owner_id=UNCHANGED,
|
||||
on_success_message=UNCHANGED,
|
||||
|
|
@ -1206,7 +1206,7 @@ class Datasette:
|
|||
"description_html": description_html,
|
||||
"parameters": parameters,
|
||||
"is_write": is_write,
|
||||
"published": published,
|
||||
"is_published": is_published,
|
||||
"source": source,
|
||||
"owner_id": owner_id,
|
||||
}
|
||||
|
|
@ -1224,7 +1224,7 @@ class Datasette:
|
|||
for field, value in fields.items():
|
||||
if value is UNCHANGED:
|
||||
continue
|
||||
if field in {"is_write", "published"}:
|
||||
if field in {"is_write", "is_published"}:
|
||||
value = int(bool(value))
|
||||
elif field == "parameters":
|
||||
value = json.dumps(list(value or []))
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ async def default_query_permissions_sql(
|
|||
'published query' AS reason
|
||||
FROM queries
|
||||
WHERE is_write = 0
|
||||
AND published = 1
|
||||
AND is_published = 1
|
||||
UNION ALL
|
||||
SELECT q.database_name AS parent, q.name AS child, 1 AS allow,
|
||||
'execute-sql allows query' AS reason
|
||||
|
|
@ -145,7 +145,7 @@ async def default_query_permissions_sql(
|
|||
ON es.parent = q.database_name
|
||||
AND es.child IS NULL
|
||||
WHERE q.is_write = 0
|
||||
AND q.published = 0
|
||||
AND q.is_published = 0
|
||||
{trusted_writable_sql}
|
||||
{user_writable_sql}
|
||||
""",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<p><textarea id="sql-editor" name="sql"{% if sql %} style="height: {{ sql.split("\n")|length + 2 }}em"{% endif %}>{{ sql }}</textarea></p>
|
||||
<p><label for="query-parameters">Parameters</label> <input id="query-parameters" name="parameters" type="text" value="{{ parameter_names|join(', ') }}"></p>
|
||||
{% if can_publish %}
|
||||
<p><label><input type="checkbox" name="published" value="1"> Published</label></p>
|
||||
<p><label><input type="checkbox" name="is_published" value="1"> Published</label></p>
|
||||
{% endif %}
|
||||
{% if sql and analysis_is_write %}
|
||||
<p><a href="{{ urls.database(database) }}/-/execute-write?{{ {'sql': sql}|urlencode|safe }}">Execute write SQL</a></p>
|
||||
|
|
|
|||
|
|
@ -123,13 +123,13 @@ async def initialize_metadata_tables(db):
|
|||
options TEXT NOT NULL DEFAULT '{}',
|
||||
parameters TEXT NOT NULL DEFAULT '[]',
|
||||
is_write INTEGER NOT NULL DEFAULT 0 CHECK (is_write IN (0, 1)),
|
||||
published INTEGER NOT NULL DEFAULT 0 CHECK (published IN (0, 1)),
|
||||
is_published INTEGER NOT NULL DEFAULT 0 CHECK (is_published IN (0, 1)),
|
||||
source TEXT NOT NULL DEFAULT 'user',
|
||||
owner_id TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (database_name, name),
|
||||
CHECK (is_write = 0 OR published = 0)
|
||||
CHECK (is_write = 0 OR is_published = 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS queries_owner_idx
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ _query_fields = {
|
|||
"fragment",
|
||||
"parameters",
|
||||
"params",
|
||||
"published",
|
||||
"is_published",
|
||||
"on_success_message",
|
||||
"on_success_message_sql",
|
||||
"on_success_redirect",
|
||||
|
|
@ -549,7 +549,7 @@ async def _check_query_name(db, name, *, existing=False):
|
|||
raise QueryValidationError("Query name conflicts with a table or view")
|
||||
|
||||
|
||||
async def _analyze_user_query(datasette, db, sql, *, actor, published):
|
||||
async def _analyze_user_query(datasette, db, sql, *, actor, is_published):
|
||||
if not sql or not isinstance(sql, str):
|
||||
raise QueryValidationError("SQL is required")
|
||||
derived = _derived_query_parameters(sql)
|
||||
|
|
@ -561,7 +561,7 @@ async def _analyze_user_query(datasette, db, sql, *, actor, published):
|
|||
|
||||
is_write = _analysis_is_write(analysis)
|
||||
if is_write:
|
||||
if published:
|
||||
if is_published:
|
||||
raise QueryValidationError("Writable queries cannot be published")
|
||||
try:
|
||||
await datasette.ensure_query_write_permissions(
|
||||
|
|
@ -660,7 +660,7 @@ async def _prepare_execute_write(datasette, db, sql, params, actor):
|
|||
|
||||
def _apply_query_data_types(data):
|
||||
typed = dict(data)
|
||||
for key in ("hide_sql", "published"):
|
||||
for key in ("hide_sql", "is_published"):
|
||||
if key in typed:
|
||||
typed[key] = _as_bool(typed[key])
|
||||
return typed
|
||||
|
|
@ -677,15 +677,15 @@ async def _prepare_query_create(datasette, request, db, data):
|
|||
if await datasette.get_query(db.name, name) is not None:
|
||||
raise QueryValidationError("Query already exists")
|
||||
|
||||
published = _as_bool(data.get("published"))
|
||||
is_published = _as_bool(data.get("is_published"))
|
||||
is_write, derived, analysis = await _analyze_user_query(
|
||||
datasette,
|
||||
db,
|
||||
data.get("sql"),
|
||||
actor=request.actor,
|
||||
published=published,
|
||||
is_published=is_published,
|
||||
)
|
||||
if published and not await datasette.allowed(
|
||||
if is_published and not await datasette.allowed(
|
||||
action="publish-query",
|
||||
resource=DatabaseResource(db.name),
|
||||
actor=request.actor,
|
||||
|
|
@ -708,7 +708,7 @@ async def _prepare_query_create(datasette, request, db, data):
|
|||
"fragment": data.get("fragment"),
|
||||
"parameters": parameters,
|
||||
"is_write": is_write,
|
||||
"published": published,
|
||||
"is_published": is_published,
|
||||
"source": "user",
|
||||
"owner_id": _actor_id(request.actor),
|
||||
"on_success_message": data.get("on_success_message"),
|
||||
|
|
@ -727,7 +727,7 @@ async def _prepare_query_update(datasette, request, db, existing, update):
|
|||
|
||||
update = _apply_query_data_types(update)
|
||||
sql = update.get("sql", existing["sql"])
|
||||
published = update.get("published", existing["published"])
|
||||
is_published = update.get("is_published", existing["is_published"])
|
||||
query_is_write = existing["is_write"]
|
||||
derived = _derived_query_parameters(sql)
|
||||
parameters = None
|
||||
|
|
@ -738,11 +738,11 @@ async def _prepare_query_update(datasette, request, db, existing, update):
|
|||
db,
|
||||
sql,
|
||||
actor=request.actor,
|
||||
published=published,
|
||||
is_published=is_published,
|
||||
)
|
||||
elif published and query_is_write:
|
||||
elif is_published and query_is_write:
|
||||
raise QueryValidationError("Writable queries cannot be published")
|
||||
if published and not existing["published"]:
|
||||
if is_published and not existing["is_published"]:
|
||||
if not await datasette.allowed(
|
||||
action="publish-query",
|
||||
resource=DatabaseResource(db.name),
|
||||
|
|
@ -772,7 +772,7 @@ async def _prepare_query_update(datasette, request, db, existing, update):
|
|||
"fragment": update.get("fragment"),
|
||||
"parameters": parameters,
|
||||
"is_write": query_is_write,
|
||||
"published": published,
|
||||
"is_published": is_published,
|
||||
"on_success_message": update.get("on_success_message"),
|
||||
"on_success_message_sql": update.get("on_success_message_sql"),
|
||||
"on_success_redirect": update.get("on_success_redirect"),
|
||||
|
|
|
|||
|
|
@ -2158,13 +2158,13 @@ The internal database schema is as follows:
|
|||
options TEXT NOT NULL DEFAULT '{}',
|
||||
parameters TEXT NOT NULL DEFAULT '[]',
|
||||
is_write INTEGER NOT NULL DEFAULT 0 CHECK (is_write IN (0, 1)),
|
||||
published INTEGER NOT NULL DEFAULT 0 CHECK (published IN (0, 1)),
|
||||
is_published INTEGER NOT NULL DEFAULT 0 CHECK (is_published IN (0, 1)),
|
||||
source TEXT NOT NULL DEFAULT 'user',
|
||||
owner_id TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (database_name, name),
|
||||
CHECK (is_write = 0 OR published = 0)
|
||||
CHECK (is_write = 0 OR is_published = 0)
|
||||
);
|
||||
CREATE INDEX queries_owner_idx
|
||||
ON queries(owner_id);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Terminology change: these are now "queries", not "canned queries". Legacy code a
|
|||
- Internal table name: `queries`.
|
||||
- Query definitions should use real columns, not a JSON blob for all options.
|
||||
- Query parameter names live in a `parameters` text column as a JSON array. No default values for parameters in this pass.
|
||||
- No `queries_database_published_idx` index.
|
||||
- No `queries_database_is_published_idx` index.
|
||||
- User-created queries require `execute-sql` and `insert-query` on the database. Writable queries additionally require matching table write permissions discovered by `Database.analyze_sql()`.
|
||||
- `publish-query` is the permission for creating or updating a query so users without `execute-sql` can execute it.
|
||||
- Add `update-query` and `delete-query`, so administrators can manage queries created by other users.
|
||||
|
|
@ -45,13 +45,13 @@ CREATE TABLE IF NOT EXISTS queries (
|
|||
options TEXT NOT NULL DEFAULT '{}',
|
||||
parameters TEXT NOT NULL DEFAULT '[]',
|
||||
is_write INTEGER NOT NULL DEFAULT 0 CHECK (is_write IN (0, 1)),
|
||||
published INTEGER NOT NULL DEFAULT 0 CHECK (published IN (0, 1)),
|
||||
is_published INTEGER NOT NULL DEFAULT 0 CHECK (is_published IN (0, 1)),
|
||||
source TEXT NOT NULL DEFAULT 'user',
|
||||
owner_id TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (database_name, name),
|
||||
CHECK (is_write = 0 OR published = 0)
|
||||
CHECK (is_write = 0 OR is_published = 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS queries_owner_idx
|
||||
|
|
@ -65,11 +65,11 @@ Column notes:
|
|||
- Less common presentation and writable-query behavior lives in `options`, stored as a JSON object. That covers `hide_sql`, `fragment`, `on_success_message`, `on_success_message_sql`, `on_success_redirect`, `on_error_message`, and `on_error_redirect`.
|
||||
- `parameters` is a JSON array of parameter names, stored as text. This preserves explicit parameter order, but does not support labels or default values.
|
||||
- Existing writable query behavior gets `is_write` as a column. Success/error messages, success/error redirects, and `on_success_message_sql` are stored in `options`.
|
||||
- `published` only applies to read-only queries. A writable query can still be public through explicit `view-query` permissions, but the "publish for users without execute-sql" shortcut should be read-only.
|
||||
- `is_published` only applies to read-only queries. A writable query can still be public through explicit `view-query` permissions, but the "publish for users without execute-sql" shortcut should be read-only.
|
||||
- `source` distinguishes `user`, `config`, and `plugin` rows.
|
||||
- `owner_id` is the actor id for user-created rows. It is `NULL` for config/plugin rows.
|
||||
|
||||
No separate index is needed on `(database_name, name)` because the primary key already creates one. Do not add a `queries_database_published_idx` index for now.
|
||||
No separate index is needed on `(database_name, name)` because the primary key already creates one. Do not add a `queries_database_is_published_idx` index for now.
|
||||
|
||||
`QueryResource.resources_sql()` can become:
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ User-created query creation requires:
|
|||
- `insert-query` on `DatabaseResource(database)`
|
||||
- If analysis shows the query is writable, the table-level write permissions described in the writable query section.
|
||||
|
||||
Setting `published=1` requires:
|
||||
Setting `is_published=1` requires:
|
||||
|
||||
- `publish-query` on `DatabaseResource(database)`
|
||||
- The query must be read-only according to `Database.analyze_sql()`.
|
||||
|
|
@ -125,7 +125,7 @@ Updating an existing query requires:
|
|||
- `update-query` on `QueryResource(database, query)` or default owner permission for a user-owned row.
|
||||
- If the SQL changes, also require `execute-sql` on the database.
|
||||
- If the changed SQL is writable, also require the table-level write permissions described in the writable query section.
|
||||
- If `published` changes from `0` to `1`, also require `publish-query` on the database.
|
||||
- If `is_published` changes from `0` to `1`, also require `publish-query` on the database.
|
||||
|
||||
Deleting an existing query requires:
|
||||
|
||||
|
|
@ -140,12 +140,12 @@ Default owner permissions:
|
|||
|
||||
Default execution rule for read-only queries:
|
||||
|
||||
- If `published=0`, the actor needs `execute-sql` on the database.
|
||||
- If `published=1`, the actor can execute the query without `execute-sql`.
|
||||
- If `is_published=0`, the actor needs `execute-sql` on the database.
|
||||
- If `is_published=1`, the actor can execute the query without `execute-sql`.
|
||||
|
||||
Default execution rule for user-created writable queries:
|
||||
|
||||
- `published` must be `0`.
|
||||
- `is_published` must be `0`.
|
||||
- The actor must have `view-query`.
|
||||
- The actor must currently have every write permission required by fresh `Database.analyze_sql()` results for the query SQL.
|
||||
|
||||
|
|
@ -153,8 +153,8 @@ Implementation:
|
|||
|
||||
- Remove `view-query` from the broad `DEFAULT_ALLOW_ACTIONS` set.
|
||||
- Replace it with query-aware default `view-query` permission SQL.
|
||||
- For `published=1 AND is_write=0`, emit a child-level `view-query` allow.
|
||||
- For `published=0 AND is_write=0`, emit child-level `view-query` allows for queries whose parent database is in the actor's `execute-sql` allowed resources.
|
||||
- For `is_published=1 AND is_write=0`, emit a child-level `view-query` allow.
|
||||
- For `is_published=0 AND is_write=0`, emit child-level `view-query` allows for queries whose parent database is in the actor's `execute-sql` allowed resources.
|
||||
- For `is_write=1 AND source='user'`, emit `view-query` only for the owner or actors with explicit `view-query` permission, then have `QueryView` perform the fresh analysis/table-permission check before execution.
|
||||
- For trusted writable queries, preserve current behavior by emitting child-level `view-query` allows for `is_write=1 AND source IN ('config', 'plugin')` when Datasette is not running with `--default-deny`.
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ Validation flow for user-created queries:
|
|||
1. Derive named parameters from the SQL and pass harmless placeholder values into `db.analyze_sql()` so SQLite can prepare statements with bindings.
|
||||
2. If analysis raises a SQLite error, reject the query.
|
||||
3. If every table access is `read`, treat the query as read-only and require `execute-sql` plus `insert-query`/`update-query` as described above.
|
||||
4. If any table access is `insert`, `update`, or `delete`, treat the query as writable and force `published=0`.
|
||||
4. If any table access is `insert`, `update`, or `delete`, treat the query as writable and force `is_published=0`.
|
||||
5. Reject writable user-created queries that access a database other than the database they are being saved against, until `analyze_sql()` can reliably map attached SQLite schemas back to Datasette database names.
|
||||
6. For every write access returned by analysis, require the corresponding permission on `TableResource(access.database, access.table)`:
|
||||
- `insert` -> `insert-row`
|
||||
|
|
@ -201,7 +201,7 @@ Fail closed cases for user-created writable queries:
|
|||
- Analysis reports any write operation that cannot be mapped to a Datasette table resource.
|
||||
- Analysis reports writes outside the target database.
|
||||
- The actor lacks any required table write permission.
|
||||
- `published=1` is requested.
|
||||
- `is_published=1` is requested.
|
||||
|
||||
This gives us writable user-created queries without letting `execute-sql` alone become a path to create arbitrary write endpoints.
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ Create request:
|
|||
"sql": "select * from customers order by revenue desc limit 20",
|
||||
"title": "Top customers",
|
||||
"description": "Highest revenue customers",
|
||||
"published": false,
|
||||
"is_published": false,
|
||||
"parameters": ["region"]
|
||||
}
|
||||
}
|
||||
|
|
@ -243,7 +243,7 @@ Successful create returns `201` and the created query definition:
|
|||
"sql": "select * from customers order by revenue desc limit 20",
|
||||
"title": "Top customers",
|
||||
"description": "Highest revenue customers",
|
||||
"published": false,
|
||||
"is_published": false,
|
||||
"parameters": ["region"]
|
||||
}
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@ Update request, imitating `RowUpdateView`:
|
|||
{
|
||||
"update": {
|
||||
"title": "Top customers by revenue",
|
||||
"published": true
|
||||
"is_published": true
|
||||
},
|
||||
"return": true
|
||||
}
|
||||
|
|
@ -271,7 +271,7 @@ Successful update returns `{"ok": true}` by default. With `"return": true`, retu
|
|||
"name": "top_customers",
|
||||
"sql": "select * from customers order by revenue desc limit 20",
|
||||
"title": "Top customers by revenue",
|
||||
"published": true
|
||||
"is_published": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -318,7 +318,7 @@ await datasette.add_query(
|
|||
fragment=None,
|
||||
parameters=None,
|
||||
is_write=False,
|
||||
published=False,
|
||||
is_published=False,
|
||||
source="plugin",
|
||||
owner_id=None,
|
||||
on_success_message=None,
|
||||
|
|
@ -341,7 +341,7 @@ await datasette.update_query(
|
|||
fragment=UNCHANGED,
|
||||
parameters=UNCHANGED,
|
||||
is_write=UNCHANGED,
|
||||
published=UNCHANGED,
|
||||
is_published=UNCHANGED,
|
||||
source=UNCHANGED,
|
||||
owner_id=UNCHANGED,
|
||||
on_success_message=UNCHANGED,
|
||||
|
|
@ -371,13 +371,13 @@ For column-backed fields, `None` should write SQL `NULL`. For option fields, `No
|
|||
|
||||
Implementation detail: build the `UPDATE` statement dynamically from fields whose value is not `UNCHANGED`, validate non-nullable fields before writing, and update `updated_at` whenever at least one field changes.
|
||||
|
||||
The read methods should reconstruct the existing dictionary shape used by query execution and templates, with `name`, `sql`, display fields, write fields, `params`, `published`, `owner_id`, and `source`. `parameters` should be returned as the decoded JSON array and exposed as `params` where existing query execution code expects that key. Option values should be unpacked from the `options` JSON object and returned as the same top-level keys accepted by `add_query()` and `update_query()`.
|
||||
The read methods should reconstruct the existing dictionary shape used by query execution and templates, with `name`, `sql`, display fields, write fields, `params`, `is_published`, `owner_id`, and `source`. `parameters` should be returned as the decoded JSON array and exposed as `params` where existing query execution code expects that key. Option values should be unpacked from the `options` JSON object and returned as the same top-level keys accepted by `add_query()` and `update_query()`.
|
||||
|
||||
## Query page save UI
|
||||
|
||||
On `/{database}/-/query`, if the actor has both `execute-sql` and `insert-query`, show a save control for valid read-only SQL. That page already executes read-only arbitrary SQL, so the first UI can stay read-only even though the JSON API can accept writable SQL after `Database.analyze_sql()` validation.
|
||||
|
||||
The save form should call `POST /{database}/-/queries/-/insert` and default to `published=false`.
|
||||
The save form should call `POST /{database}/-/queries/-/insert` and default to `is_published=false`.
|
||||
|
||||
If the actor also has `publish-query`, include a publish control. The UI copy should make it clear that publishing allows people without arbitrary SQL permission to run this query.
|
||||
|
||||
|
|
@ -416,7 +416,7 @@ The existing edit-SQL flow from query pages can continue to point back to arbitr
|
|||
- `view-query` is no longer globally default-allowed; default query permissions come from the query-aware hook.
|
||||
- Unpublished read-only query requires `execute-sql` to execute.
|
||||
- Published read-only query can be executed without `execute-sql`.
|
||||
- Setting `published=true` requires `publish-query`.
|
||||
- Setting `is_published=true` requires `publish-query`.
|
||||
- User-created query requires both `execute-sql` and `insert-query`.
|
||||
- User-created writable query creation uses `Database.analyze_sql()` and requires matching `insert-row`, `update-row`, and/or `delete-row` permissions for every reported write access.
|
||||
- `/{database}/-/queries/-/create` provides the writable-query authoring UI with an analysis panel and disabled save until all required write permissions pass.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ async def test_queries_internal_table_schema():
|
|||
"options",
|
||||
"parameters",
|
||||
"is_write",
|
||||
"published",
|
||||
"is_published",
|
||||
"source",
|
||||
"owner_id",
|
||||
"created_at",
|
||||
|
|
@ -53,7 +53,7 @@ async def test_add_get_and_remove_query():
|
|||
hide_sql=True,
|
||||
fragment="chart",
|
||||
parameters=["region"],
|
||||
published=True,
|
||||
is_published=True,
|
||||
source="user",
|
||||
owner_id="alice",
|
||||
)
|
||||
|
|
@ -86,7 +86,7 @@ async def test_add_get_and_remove_query():
|
|||
"parameters": ["region"],
|
||||
"is_write": False,
|
||||
"write": False,
|
||||
"published": True,
|
||||
"is_published": True,
|
||||
"source": "user",
|
||||
"owner_id": "alice",
|
||||
"on_success_message": None,
|
||||
|
|
@ -143,7 +143,7 @@ async def test_update_query_only_updates_provided_fields():
|
|||
assert query["params"] == []
|
||||
assert query["on_success_redirect"] is None
|
||||
assert query["sql"] == "select 1"
|
||||
assert query["published"] is False
|
||||
assert query["is_published"] is False
|
||||
options_row = (
|
||||
await ds.get_internal_database().execute(
|
||||
"""
|
||||
|
|
@ -190,7 +190,7 @@ async def test_config_queries_imported_to_internal_table():
|
|||
"parameters": ["name"],
|
||||
"is_write": False,
|
||||
"write": False,
|
||||
"published": False,
|
||||
"is_published": False,
|
||||
"source": "config",
|
||||
"owner_id": None,
|
||||
"on_success_message": None,
|
||||
|
|
@ -218,8 +218,8 @@ async def test_unpublished_query_requires_execute_sql_but_published_does_not():
|
|||
ds = Datasette(memory=True, settings={"default_allow_sql": False})
|
||||
ds.add_memory_database("query_permissions", name="data")
|
||||
await ds.invoke_startup()
|
||||
await ds.add_query("data", "unpublished", "select 1", published=False)
|
||||
await ds.add_query("data", "published", "select 1", published=True)
|
||||
await ds.add_query("data", "unpublished", "select 1", is_published=False)
|
||||
await ds.add_query("data", "published", "select 1", is_published=True)
|
||||
|
||||
assert not await ds.allowed(
|
||||
action="execute-sql",
|
||||
|
|
@ -347,7 +347,7 @@ async def test_query_list_and_definition_api():
|
|||
ds.root_enabled = True
|
||||
ds.add_memory_database("query_list_api", name="data")
|
||||
await ds.invoke_startup()
|
||||
await ds.add_query("data", "listed", "select 1", title="Listed", published=True)
|
||||
await ds.add_query("data", "listed", "select 1", title="Listed", is_published=True)
|
||||
|
||||
list_response = await ds.client.get(
|
||||
"/data/-/queries",
|
||||
|
|
@ -387,7 +387,7 @@ async def test_query_insert_api_publish_requires_publish_query():
|
|||
response = await ds.client.post(
|
||||
"/data/-/queries/-/insert",
|
||||
actor={"id": "writer"},
|
||||
json={"query": {"name": "public", "sql": "select 1", "published": True}},
|
||||
json={"query": {"name": "public", "sql": "select 1", "is_published": True}},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
|
@ -416,7 +416,7 @@ async def test_query_insert_api_creates_writable_query():
|
|||
assert response.status_code == 201
|
||||
query = response.json()["query"]
|
||||
assert query["is_write"] is True
|
||||
assert query["published"] is False
|
||||
assert query["is_published"] is False
|
||||
assert query["parameters"] == ["name"]
|
||||
|
||||
bad_response = await ds.client.post(
|
||||
|
|
@ -426,7 +426,7 @@ async def test_query_insert_api_creates_writable_query():
|
|||
"query": {
|
||||
"name": "published_insert",
|
||||
"sql": "insert into dogs (name) values (:name)",
|
||||
"published": True,
|
||||
"is_published": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue