kopia lustrzana https://github.com/simonw/datasette
on_success_message_sql, closes #2138
rodzic
4a42476bb7
commit
a3593c9015
|
@ -360,6 +360,10 @@ class QueryView(View):
|
||||||
params[key] = str(value)
|
params[key] = str(value)
|
||||||
else:
|
else:
|
||||||
params = dict(parse_qsl(body, keep_blank_values=True))
|
params = dict(parse_qsl(body, keep_blank_values=True))
|
||||||
|
|
||||||
|
# Don't ever send csrftoken as a SQL parameter
|
||||||
|
params.pop("csrftoken", None)
|
||||||
|
|
||||||
# Should we return JSON?
|
# Should we return JSON?
|
||||||
should_return_json = (
|
should_return_json = (
|
||||||
request.headers.get("accept") == "application/json"
|
request.headers.get("accept") == "application/json"
|
||||||
|
@ -371,12 +375,27 @@ class QueryView(View):
|
||||||
redirect_url = None
|
redirect_url = None
|
||||||
try:
|
try:
|
||||||
cursor = await db.execute_write(canned_query["sql"], params_for_query)
|
cursor = await db.execute_write(canned_query["sql"], params_for_query)
|
||||||
|
# success message can come from on_success_message or on_success_message_sql
|
||||||
|
message = None
|
||||||
|
message_type = datasette.INFO
|
||||||
|
on_success_message_sql = canned_query.get("on_success_message_sql")
|
||||||
|
if on_success_message_sql:
|
||||||
|
try:
|
||||||
|
message_result = (
|
||||||
|
await db.execute(on_success_message_sql, params_for_query)
|
||||||
|
).first()
|
||||||
|
if message_result:
|
||||||
|
message = message_result[0]
|
||||||
|
except Exception as ex:
|
||||||
|
message = "Error running on_success_message_sql: {}".format(ex)
|
||||||
|
message_type = datasette.ERROR
|
||||||
|
if not message:
|
||||||
message = canned_query.get(
|
message = canned_query.get(
|
||||||
"on_success_message"
|
"on_success_message"
|
||||||
) or "Query executed, {} row{} affected".format(
|
) or "Query executed, {} row{} affected".format(
|
||||||
cursor.rowcount, "" if cursor.rowcount == 1 else "s"
|
cursor.rowcount, "" if cursor.rowcount == 1 else "s"
|
||||||
)
|
)
|
||||||
message_type = datasette.INFO
|
|
||||||
redirect_url = canned_query.get("on_success_redirect")
|
redirect_url = canned_query.get("on_success_redirect")
|
||||||
ok = True
|
ok = True
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
|
|
@ -392,6 +392,7 @@ This configuration will create a page at ``/mydatabase/add_name`` displaying a f
|
||||||
You can customize how Datasette represents success and errors using the following optional properties:
|
You can customize how Datasette represents success and errors using the following optional properties:
|
||||||
|
|
||||||
- ``on_success_message`` - the message shown when a query is successful
|
- ``on_success_message`` - the message shown when a query is successful
|
||||||
|
- ``on_success_message_sql`` - alternative to ``on_success_message``: a SQL query that should be executed to generate the message
|
||||||
- ``on_success_redirect`` - the path or URL the user is redirected to on success
|
- ``on_success_redirect`` - the path or URL the user is redirected to on success
|
||||||
- ``on_error_message`` - the message shown when a query throws an error
|
- ``on_error_message`` - the message shown when a query throws an error
|
||||||
- ``on_error_redirect`` - the path or URL the user is redirected to on error
|
- ``on_error_redirect`` - the path or URL the user is redirected to on error
|
||||||
|
@ -405,11 +406,12 @@ For example:
|
||||||
"queries": {
|
"queries": {
|
||||||
"add_name": {
|
"add_name": {
|
||||||
"sql": "INSERT INTO names (name) VALUES (:name)",
|
"sql": "INSERT INTO names (name) VALUES (:name)",
|
||||||
|
"params": ["name"],
|
||||||
"write": True,
|
"write": True,
|
||||||
"on_success_message": "Name inserted",
|
"on_success_message_sql": "select 'Name inserted: ' || :name",
|
||||||
"on_success_redirect": "/mydatabase/names",
|
"on_success_redirect": "/mydatabase/names",
|
||||||
"on_error_message": "Name insert failed",
|
"on_error_message": "Name insert failed",
|
||||||
"on_error_redirect": "/mydatabase"
|
"on_error_redirect": "/mydatabase",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -426,8 +428,10 @@ For example:
|
||||||
queries:
|
queries:
|
||||||
add_name:
|
add_name:
|
||||||
sql: INSERT INTO names (name) VALUES (:name)
|
sql: INSERT INTO names (name) VALUES (:name)
|
||||||
|
params:
|
||||||
|
- name
|
||||||
write: true
|
write: true
|
||||||
on_success_message: Name inserted
|
on_success_message_sql: 'select ''Name inserted: '' || :name'
|
||||||
on_success_redirect: /mydatabase/names
|
on_success_redirect: /mydatabase/names
|
||||||
on_error_message: Name insert failed
|
on_error_message: Name insert failed
|
||||||
on_error_redirect: /mydatabase
|
on_error_redirect: /mydatabase
|
||||||
|
@ -443,8 +447,11 @@ For example:
|
||||||
"queries": {
|
"queries": {
|
||||||
"add_name": {
|
"add_name": {
|
||||||
"sql": "INSERT INTO names (name) VALUES (:name)",
|
"sql": "INSERT INTO names (name) VALUES (:name)",
|
||||||
|
"params": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
"write": true,
|
"write": true,
|
||||||
"on_success_message": "Name inserted",
|
"on_success_message_sql": "select 'Name inserted: ' || :name",
|
||||||
"on_success_redirect": "/mydatabase/names",
|
"on_success_redirect": "/mydatabase/names",
|
||||||
"on_error_message": "Name insert failed",
|
"on_error_message": "Name insert failed",
|
||||||
"on_error_redirect": "/mydatabase"
|
"on_error_redirect": "/mydatabase"
|
||||||
|
@ -455,10 +462,12 @@ For example:
|
||||||
}
|
}
|
||||||
.. [[[end]]]
|
.. [[[end]]]
|
||||||
|
|
||||||
You can use ``"params"`` to explicitly list the named parameters that should be displayed as form fields - otherwise they will be automatically detected.
|
You can use ``"params"`` to explicitly list the named parameters that should be displayed as form fields - otherwise they will be automatically detected. ``"params"`` is not necessary in the above example, since without it ``"name"`` would be automatically detected from the query.
|
||||||
|
|
||||||
You can pre-populate form fields when the page first loads using a query string, e.g. ``/mydatabase/add_name?name=Prepopulated``. The user will have to submit the form to execute the query.
|
You can pre-populate form fields when the page first loads using a query string, e.g. ``/mydatabase/add_name?name=Prepopulated``. The user will have to submit the form to execute the query.
|
||||||
|
|
||||||
|
If you specify a query in ``"on_success_message_sql"``, that query will be executed after the main query. The first column of the first row return by that query will be displayed as a success message. Named parameters from the main query will be made available to the success message query as well.
|
||||||
|
|
||||||
.. _canned_queries_magic_parameters:
|
.. _canned_queries_magic_parameters:
|
||||||
|
|
||||||
Magic parameters
|
Magic parameters
|
||||||
|
@ -589,7 +598,7 @@ The JSON response will look like this:
|
||||||
"redirect": "/data/add_name"
|
"redirect": "/data/add_name"
|
||||||
}
|
}
|
||||||
|
|
||||||
The ``"message"`` and ``"redirect"`` values here will take into account ``on_success_message``, ``on_success_redirect``, ``on_error_message`` and ``on_error_redirect``, if they have been set.
|
The ``"message"`` and ``"redirect"`` values here will take into account ``on_success_message``, ``on_success_message_sql``, ``on_success_redirect``, ``on_error_message`` and ``on_error_redirect``, if they have been set.
|
||||||
|
|
||||||
.. _pagination:
|
.. _pagination:
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,15 @@ def canned_write_client(tmpdir):
|
||||||
},
|
},
|
||||||
"add_name_specify_id": {
|
"add_name_specify_id": {
|
||||||
"sql": "insert into names (rowid, name) values (:rowid, :name)",
|
"sql": "insert into names (rowid, name) values (:rowid, :name)",
|
||||||
|
"on_success_message_sql": "select 'Name added: ' || :name || ' with rowid ' || :rowid",
|
||||||
"write": True,
|
"write": True,
|
||||||
"on_error_redirect": "/data/add_name_specify_id?error",
|
"on_error_redirect": "/data/add_name_specify_id?error",
|
||||||
},
|
},
|
||||||
|
"add_name_specify_id_with_error_in_on_success_message_sql": {
|
||||||
|
"sql": "insert into names (rowid, name) values (:rowid, :name)",
|
||||||
|
"on_success_message_sql": "select this is bad SQL",
|
||||||
|
"write": True,
|
||||||
|
},
|
||||||
"delete_name": {
|
"delete_name": {
|
||||||
"sql": "delete from names where rowid = :rowid",
|
"sql": "delete from names where rowid = :rowid",
|
||||||
"write": True,
|
"write": True,
|
||||||
|
@ -179,6 +185,34 @@ def test_insert_error(canned_write_client):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_success_message_sql(canned_write_client):
|
||||||
|
response = canned_write_client.post(
|
||||||
|
"/data/add_name_specify_id",
|
||||||
|
{"rowid": 5, "name": "Should be OK"},
|
||||||
|
csrftoken_from=True,
|
||||||
|
)
|
||||||
|
assert response.status == 302
|
||||||
|
assert response.headers["Location"] == "/data/add_name_specify_id"
|
||||||
|
messages = canned_write_client.ds.unsign(
|
||||||
|
response.cookies["ds_messages"], "messages"
|
||||||
|
)
|
||||||
|
assert messages == [["Name added: Should be OK with rowid 5", 1]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_in_on_success_message_sql(canned_write_client):
|
||||||
|
response = canned_write_client.post(
|
||||||
|
"/data/add_name_specify_id_with_error_in_on_success_message_sql",
|
||||||
|
{"rowid": 1, "name": "Should fail"},
|
||||||
|
csrftoken_from=True,
|
||||||
|
)
|
||||||
|
messages = canned_write_client.ds.unsign(
|
||||||
|
response.cookies["ds_messages"], "messages"
|
||||||
|
)
|
||||||
|
assert messages == [
|
||||||
|
["Error running on_success_message_sql: no such column: bad", 3]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_custom_params(canned_write_client):
|
def test_custom_params(canned_write_client):
|
||||||
response = canned_write_client.get("/data/update_name?extra=foo")
|
response = canned_write_client.get("/data/update_name?extra=foo")
|
||||||
assert '<input type="text" id="qp3" name="extra" value="foo">' in response.text
|
assert '<input type="text" id="qp3" name="extra" value="foo">' in response.text
|
||||||
|
@ -232,21 +266,22 @@ def test_canned_query_permissions_on_database_page(canned_write_client):
|
||||||
query_names = {
|
query_names = {
|
||||||
q["name"] for q in canned_write_client.get("/data.json").json["queries"]
|
q["name"] for q in canned_write_client.get("/data.json").json["queries"]
|
||||||
}
|
}
|
||||||
assert {
|
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",
|
"canned_read",
|
||||||
"add_name",
|
"add_name",
|
||||||
"add_name_specify_id",
|
}
|
||||||
"update_name",
|
|
||||||
"from_async_hook",
|
|
||||||
"from_hook",
|
|
||||||
} == query_names
|
|
||||||
|
|
||||||
# With auth shows four
|
# With auth shows four
|
||||||
response = canned_write_client.get(
|
response = canned_write_client.get(
|
||||||
"/data.json",
|
"/data.json",
|
||||||
cookies={"ds_actor": canned_write_client.actor_cookie({"id": "root"})},
|
cookies={"ds_actor": canned_write_client.actor_cookie({"id": "root"})},
|
||||||
)
|
)
|
||||||
assert 200 == response.status
|
assert response.status == 200
|
||||||
query_names_and_private = sorted(
|
query_names_and_private = sorted(
|
||||||
[
|
[
|
||||||
{"name": q["name"], "private": q["private"]}
|
{"name": q["name"], "private": q["private"]}
|
||||||
|
@ -257,6 +292,10 @@ def test_canned_query_permissions_on_database_page(canned_write_client):
|
||||||
assert query_names_and_private == [
|
assert query_names_and_private == [
|
||||||
{"name": "add_name", "private": False},
|
{"name": "add_name", "private": False},
|
||||||
{"name": "add_name_specify_id", "private": False},
|
{"name": "add_name_specify_id", "private": False},
|
||||||
|
{
|
||||||
|
"name": "add_name_specify_id_with_error_in_on_success_message_sql",
|
||||||
|
"private": False,
|
||||||
|
},
|
||||||
{"name": "canned_read", "private": False},
|
{"name": "canned_read", "private": False},
|
||||||
{"name": "delete_name", "private": True},
|
{"name": "delete_name", "private": True},
|
||||||
{"name": "from_async_hook", "private": False},
|
{"name": "from_async_hook", "private": False},
|
||||||
|
|
Ładowanie…
Reference in New Issue