kopia lustrzana https://github.com/simonw/datasette
JSON API for writable canned queries, closes #880
rodzic
894999a14e
commit
72ac2fd32c
|
@ -55,6 +55,7 @@ class TestClient:
|
|||
redirect_count=0,
|
||||
content_type="application/x-www-form-urlencoded",
|
||||
cookies=None,
|
||||
headers=None,
|
||||
csrftoken_from=None,
|
||||
):
|
||||
cookies = cookies or {}
|
||||
|
@ -72,13 +73,14 @@ class TestClient:
|
|||
if post_data:
|
||||
body = urlencode(post_data, doseq=True)
|
||||
return await self._request(
|
||||
path,
|
||||
allow_redirects,
|
||||
redirect_count,
|
||||
"POST",
|
||||
cookies,
|
||||
body,
|
||||
content_type,
|
||||
path=path,
|
||||
allow_redirects=allow_redirects,
|
||||
redirect_count=redirect_count,
|
||||
method="POST",
|
||||
cookies=cookies,
|
||||
headers=headers,
|
||||
post_body=body,
|
||||
content_type=content_type,
|
||||
)
|
||||
|
||||
async def _request(
|
||||
|
@ -88,6 +90,7 @@ class TestClient:
|
|||
redirect_count=0,
|
||||
method="GET",
|
||||
cookies=None,
|
||||
headers=None,
|
||||
post_body=None,
|
||||
content_type=None,
|
||||
):
|
||||
|
@ -99,14 +102,17 @@ class TestClient:
|
|||
raw_path = path.encode("latin-1")
|
||||
else:
|
||||
raw_path = quote(path, safe="/:,").encode("latin-1")
|
||||
headers = [[b"host", b"localhost"]]
|
||||
asgi_headers = [[b"host", b"localhost"]]
|
||||
if headers:
|
||||
for key, value in headers.items():
|
||||
asgi_headers.append([key.encode("utf-8"), value.encode("utf-8")])
|
||||
if content_type:
|
||||
headers.append((b"content-type", content_type.encode("utf-8")))
|
||||
asgi_headers.append((b"content-type", content_type.encode("utf-8")))
|
||||
if cookies:
|
||||
sc = SimpleCookie()
|
||||
for key, value in cookies.items():
|
||||
sc[key] = value
|
||||
headers.append([b"cookie", sc.output(header="").encode("utf-8")])
|
||||
asgi_headers.append([b"cookie", sc.output(header="").encode("utf-8")])
|
||||
scope = {
|
||||
"type": "http",
|
||||
"http_version": "1.0",
|
||||
|
@ -114,7 +120,7 @@ class TestClient:
|
|||
"path": unquote(path),
|
||||
"raw_path": raw_path,
|
||||
"query_string": query_string,
|
||||
"headers": headers,
|
||||
"headers": asgi_headers,
|
||||
}
|
||||
instance = ApplicationCommunicator(self.asgi_app, scope)
|
||||
|
||||
|
|
|
@ -219,10 +219,17 @@ class QueryView(DataView):
|
|||
params[key] = str(value)
|
||||
else:
|
||||
params = dict(parse_qsl(body, keep_blank_values=True))
|
||||
# Should we return JSON?
|
||||
should_return_json = (
|
||||
request.headers.get("accept") == "application/json"
|
||||
or request.args.get("_json")
|
||||
or params.get("_json")
|
||||
)
|
||||
if canned_query:
|
||||
params_for_query = MagicParameters(params, request, self.ds)
|
||||
else:
|
||||
params_for_query = params
|
||||
ok = None
|
||||
try:
|
||||
cursor = await self.ds.databases[database].execute_write(
|
||||
sql, params_for_query, block=True
|
||||
|
@ -234,12 +241,23 @@ class QueryView(DataView):
|
|||
)
|
||||
message_type = self.ds.INFO
|
||||
redirect_url = metadata.get("on_success_redirect")
|
||||
ok = True
|
||||
except Exception as e:
|
||||
message = metadata.get("on_error_message") or str(e)
|
||||
message_type = self.ds.ERROR
|
||||
redirect_url = metadata.get("on_error_redirect")
|
||||
self.ds.add_message(request, message, message_type)
|
||||
return self.redirect(request, redirect_url or request.path)
|
||||
ok = False
|
||||
if should_return_json:
|
||||
return Response.json(
|
||||
{
|
||||
"ok": ok,
|
||||
"message": message,
|
||||
"redirect": redirect_url,
|
||||
}
|
||||
)
|
||||
else:
|
||||
self.ds.add_message(request, message, message_type)
|
||||
return self.redirect(request, redirect_url or request.path)
|
||||
else:
|
||||
|
||||
async def extra_template():
|
||||
|
|
|
@ -326,6 +326,43 @@ The form presented at ``/mydatabase/add_message`` will have just a field for ``m
|
|||
|
||||
Additional custom magic parameters can be added by plugins using the :ref:`plugin_hook_register_magic_parameters` hook.
|
||||
|
||||
.. _canned_queries_json_api:
|
||||
|
||||
JSON API for writable canned queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Writable canned queries can also be accessed using a JSON API. You can POST data to them using JSON, and you can request that their response is returned to you as JSON.
|
||||
|
||||
To submit JSON to a writable canned query, encode key/value parameters as a JSON document::
|
||||
|
||||
POST /mydatabase/add_message
|
||||
|
||||
{"message": "Message goes here"}
|
||||
|
||||
You can also continue to submit data using regular form encoding, like so::
|
||||
|
||||
POST /mydatabase/add_message
|
||||
|
||||
message=Message+goes+here
|
||||
|
||||
There are three options for specifying that you would like the response to your request to return JSON data, as opposed to an HTTP redirect to another page.
|
||||
|
||||
- Set an ``Accept: application/json`` header on your request
|
||||
- Include ``?_json=1`` in the URL that you POST to
|
||||
- Include ``"_json": 1`` in your JSON body, or ``&_json=1`` in your form encoded body
|
||||
|
||||
The JSON response will look like this:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"ok": true,
|
||||
"message": "Query executed, 1 row affected",
|
||||
"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.
|
||||
|
||||
.. _pagination:
|
||||
|
||||
Pagination
|
||||
|
|
|
@ -176,6 +176,33 @@ def test_json_post_body(canned_write_client):
|
|||
assert rows == [{"rowid": 1, "name": "['Hello', 'there']"}]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"headers,body,querystring",
|
||||
(
|
||||
(None, "name=NameGoesHere", "?_json=1"),
|
||||
({"Accept": "application/json"}, "name=NameGoesHere", None),
|
||||
(None, "name=NameGoesHere&_json=1", None),
|
||||
(None, '{"name": "NameGoesHere", "_json": 1}', None),
|
||||
),
|
||||
)
|
||||
def test_json_response(canned_write_client, headers, body, querystring):
|
||||
response = canned_write_client.post(
|
||||
"/data/add_name" + (querystring or ""),
|
||||
body=body,
|
||||
allow_redirects=False,
|
||||
headers=headers,
|
||||
)
|
||||
assert 200 == response.status
|
||||
assert response.headers["content-type"] == "application/json; charset=utf-8"
|
||||
assert response.json == {
|
||||
"ok": True,
|
||||
"message": "Query executed, 1 row affected",
|
||||
"redirect": "/data/add_name?success",
|
||||
}
|
||||
rows = canned_write_client.get("/data/names.json?_shape=array").json
|
||||
assert rows == [{"rowid": 1, "name": "NameGoesHere"}]
|
||||
|
||||
|
||||
def test_canned_query_permissions_on_database_page(canned_write_client):
|
||||
# Without auth only shows three queries
|
||||
query_names = {
|
||||
|
@ -196,7 +223,14 @@ def test_canned_query_permissions_on_database_page(canned_write_client):
|
|||
cookies={"ds_actor": canned_write_client.actor_cookie({"id": "root"})},
|
||||
)
|
||||
assert 200 == response.status
|
||||
assert [
|
||||
query_names_and_private = sorted(
|
||||
[
|
||||
{"name": q["name"], "private": q["private"]}
|
||||
for q in response.json["queries"]
|
||||
],
|
||||
key=lambda q: q["name"],
|
||||
)
|
||||
assert query_names_and_private == [
|
||||
{"name": "add_name", "private": False},
|
||||
{"name": "add_name_specify_id", "private": False},
|
||||
{"name": "canned_read", "private": False},
|
||||
|
@ -204,13 +238,7 @@ def test_canned_query_permissions_on_database_page(canned_write_client):
|
|||
{"name": "from_async_hook", "private": False},
|
||||
{"name": "from_hook", "private": False},
|
||||
{"name": "update_name", "private": False},
|
||||
] == sorted(
|
||||
[
|
||||
{"name": q["name"], "private": q["private"]}
|
||||
for q in response.json["queries"]
|
||||
],
|
||||
key=lambda q: q["name"],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_canned_query_permissions(canned_write_client):
|
||||
|
|
Ładowanie…
Reference in New Issue