JSON API for writable canned queries, closes #880

pull/977/head
Simon Willison 2020-09-14 14:23:18 -07:00
rodzic 894999a14e
commit 72ac2fd32c
4 zmienionych plików z 110 dodań i 21 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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():

Wyświetl plik

@ -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

Wyświetl plik

@ -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):