mirror of
https://github.com/simonw/datasette.git
synced 2026-10-19 07:47:00 +02:00
Merge cf6357cf93 into d0836ddedc
This commit is contained in:
commit
dec829d7ae
10 changed files with 180 additions and 7 deletions
|
|
@ -1004,12 +1004,12 @@ def path_with_format(
|
|||
qs = extra_qs or {}
|
||||
if path is None and request:
|
||||
path = request.path
|
||||
if replace_format is None and request:
|
||||
# Drop the extension the current request used, e.g. .json
|
||||
replace_format = request.url_vars.get("format")
|
||||
if replace_format and path.endswith(f".{replace_format}"):
|
||||
path = path[: -(1 + len(replace_format))]
|
||||
if "." in path:
|
||||
qs["_format"] = format
|
||||
else:
|
||||
path = f"{path}.{format}"
|
||||
path = f"{path}.{format}"
|
||||
if qs:
|
||||
extra = urllib.parse.urlencode(sorted(qs.items()))
|
||||
if request and request.query_string:
|
||||
|
|
@ -1021,6 +1021,17 @@ def path_with_format(
|
|||
return path
|
||||
|
||||
|
||||
def path_without_format(request, *, include_query_string=False):
|
||||
"Path for this request with any ``.format`` removed, e.g. ``/db/table.json`` -> ``/db/table``"
|
||||
path = request.path
|
||||
format = request.url_vars.get("format")
|
||||
if format and path.endswith(f".{format}"):
|
||||
path = path[: -(1 + len(format))]
|
||||
if include_query_string and request.query_string:
|
||||
path = f"{path}?{request.query_string}"
|
||||
return path
|
||||
|
||||
|
||||
class CustomRow(OrderedDict):
|
||||
# Loose imitation of sqlite3.Row which offers
|
||||
# both index-based AND key-based lookups
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from datasette.utils import (
|
|||
path_with_added_args,
|
||||
path_with_format,
|
||||
path_with_removed_args,
|
||||
path_without_format,
|
||||
sqlite3,
|
||||
tilde_decode,
|
||||
to_css_class,
|
||||
|
|
@ -902,6 +903,8 @@ class QueryView(View):
|
|||
database=database,
|
||||
table=None,
|
||||
request=request,
|
||||
path=path_without_format(request),
|
||||
full_path=path_without_format(request, include_query_string=True),
|
||||
view_name="table",
|
||||
truncated=results.truncated if results else False,
|
||||
error=query_error,
|
||||
|
|
@ -975,6 +978,8 @@ class QueryView(View):
|
|||
database=database,
|
||||
table=data.get("table"),
|
||||
request=request,
|
||||
path=path_without_format(request),
|
||||
full_path=path_without_format(request, include_query_string=True),
|
||||
view_name="database",
|
||||
)
|
||||
it_can_render = await await_me_maybe(it_can_render)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from datasette.utils import (
|
|||
path_from_row_pks,
|
||||
path_with_format,
|
||||
path_with_removed_args,
|
||||
path_without_format,
|
||||
sqlite3,
|
||||
tilde_decode,
|
||||
to_css_class,
|
||||
|
|
@ -235,6 +236,8 @@ class RowView(BaseView):
|
|||
database=database,
|
||||
table=data.get("table"),
|
||||
request=request,
|
||||
path=path_without_format(request),
|
||||
full_path=path_without_format(request, include_query_string=True),
|
||||
view_name=self.name,
|
||||
truncated=False, # TODO: support this
|
||||
error=data.get("error"),
|
||||
|
|
@ -298,6 +301,8 @@ class RowView(BaseView):
|
|||
database=data.get("database"),
|
||||
table=data.get("table"),
|
||||
request=request,
|
||||
path=path_without_format(request),
|
||||
full_path=path_without_format(request, include_query_string=True),
|
||||
view_name=self.name,
|
||||
)
|
||||
it_can_render = await await_me_maybe(it_can_render)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ from datasette.utils import (
|
|||
path_with_format,
|
||||
path_with_removed_args,
|
||||
path_with_replaced_args,
|
||||
path_without_format,
|
||||
sqlite3,
|
||||
tilde_encode,
|
||||
to_css_class,
|
||||
|
|
@ -1856,6 +1857,8 @@ async def table_view_traced(datasette, request):
|
|||
database=resolved.db.name,
|
||||
table=resolved.table,
|
||||
request=request,
|
||||
path=path_without_format(request),
|
||||
full_path=path_without_format(request, include_query_string=True),
|
||||
view_name="table",
|
||||
truncated=False,
|
||||
error=None,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from datasette.utils import (
|
|||
path_with_added_args,
|
||||
path_with_format,
|
||||
path_with_removed_args,
|
||||
path_without_format,
|
||||
to_css_class,
|
||||
)
|
||||
|
||||
|
|
@ -1130,6 +1131,10 @@ class RenderersExtra(Extra):
|
|||
database=context.database_name,
|
||||
table=table_name,
|
||||
request=context.request,
|
||||
path=path_without_format(context.request),
|
||||
full_path=path_without_format(
|
||||
context.request, include_query_string=True
|
||||
),
|
||||
view_name=view_name,
|
||||
)
|
||||
it_can_render = await await_me_maybe(it_can_render)
|
||||
|
|
|
|||
|
|
@ -753,7 +753,7 @@ The available table extras are listed below.
|
|||
.. code-block:: json
|
||||
|
||||
{
|
||||
"json": "/fixtures/facetable.json?_extra=renderers&_format=json&_labels=on"
|
||||
"json": "/fixtures/facetable.json?_extra=renderers&_labels=on"
|
||||
}
|
||||
|
||||
``custom_table_templates``
|
||||
|
|
|
|||
|
|
@ -625,6 +625,12 @@ When a request is received, the ``"render"`` callback function is called with ze
|
|||
``request`` - :ref:`internals_request`
|
||||
The current HTTP request.
|
||||
|
||||
``path`` - string
|
||||
The path to the HTML version of this page, without the ``.extension`` - for example ``/fixtures/facetable`` for a request to ``/fixtures/facetable.test``. This includes the :ref:`setting_base_url` prefix if one is configured.
|
||||
|
||||
``full_path`` - string
|
||||
The same as ``path`` but with the query string included, if there was one - for example ``/fixtures/facetable?_sort=pk``. Use ``datasette.absolute_url(request, full_path)`` to turn this into a full URL.
|
||||
|
||||
``error`` - string or None
|
||||
If an error occurred this string will contain the error message.
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,17 @@ from datasette.utils.asgi import Response
|
|||
|
||||
|
||||
async def can_render(
|
||||
datasette, columns, rows, sql, query_name, database, table, request, view_name
|
||||
datasette,
|
||||
columns,
|
||||
rows,
|
||||
sql,
|
||||
query_name,
|
||||
database,
|
||||
table,
|
||||
request,
|
||||
path,
|
||||
full_path,
|
||||
view_name,
|
||||
):
|
||||
# We stash this on datasette so the calling unit test can see it
|
||||
datasette._can_render_saw = {
|
||||
|
|
@ -17,13 +27,26 @@ async def can_render(
|
|||
"database": database,
|
||||
"table": table,
|
||||
"request": request,
|
||||
"path": path,
|
||||
"full_path": full_path,
|
||||
"view_name": view_name,
|
||||
}
|
||||
return not request.args.get("_no_can_render")
|
||||
|
||||
|
||||
async def render_test_all_parameters(
|
||||
datasette, columns, rows, sql, query_name, database, table, request, view_name, data
|
||||
datasette,
|
||||
columns,
|
||||
rows,
|
||||
sql,
|
||||
query_name,
|
||||
database,
|
||||
table,
|
||||
request,
|
||||
path,
|
||||
full_path,
|
||||
view_name,
|
||||
data,
|
||||
):
|
||||
headers = {}
|
||||
for custom_header in request.args.getlist("header"):
|
||||
|
|
@ -41,6 +64,8 @@ async def render_test_all_parameters(
|
|||
"database": database,
|
||||
"table": table,
|
||||
"request": request,
|
||||
"path": path,
|
||||
"full_path": full_path,
|
||||
"view_name": view_name,
|
||||
"1+1": result.first()[0],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -624,11 +624,50 @@ async def test_hook_register_output_renderer_all_parameters(ds_client):
|
|||
"database": "fixtures",
|
||||
"table": "facetable",
|
||||
"request": '<asgi.Request method="GET" url="http://localhost/fixtures/facetable.testall">',
|
||||
"path": "/fixtures/facetable",
|
||||
"full_path": "/fixtures/facetable",
|
||||
"view_name": "table",
|
||||
"1+1": 2,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_path,expected_full_path",
|
||||
(
|
||||
(
|
||||
"/fixtures/facetable.testall?_sort_desc=pk&_size=2",
|
||||
"/fixtures/facetable",
|
||||
"/fixtures/facetable?_sort_desc=pk&_size=2",
|
||||
),
|
||||
(
|
||||
"/fixtures/simple_primary_key/1.testall",
|
||||
"/fixtures/simple_primary_key/1",
|
||||
"/fixtures/simple_primary_key/1",
|
||||
),
|
||||
(
|
||||
"/fixtures/-/query.testall?sql=select+1",
|
||||
"/fixtures/-/query",
|
||||
"/fixtures/-/query?sql=select+1",
|
||||
),
|
||||
(
|
||||
"/fixtures/pragma_cache_size.testall",
|
||||
"/fixtures/pragma_cache_size",
|
||||
"/fixtures/pragma_cache_size",
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_hook_register_output_renderer_path_and_full_path(
|
||||
ds_client, path, expected_path, expected_full_path
|
||||
):
|
||||
# https://github.com/simonw/datasette/issues/1179
|
||||
response = await ds_client.get(path)
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.text)
|
||||
assert data["path"] == expected_path
|
||||
assert data["full_path"] == expected_full_path
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hook_register_output_renderer_custom_status_code(ds_client):
|
||||
response = await ds_client.get(
|
||||
|
|
@ -701,10 +740,34 @@ async def test_hook_register_output_renderer_can_render(ds_client):
|
|||
"query_name": None,
|
||||
"database": "fixtures",
|
||||
"table": "facetable",
|
||||
"path": "/fixtures/facetable",
|
||||
"full_path": "/fixtures/facetable?_no_can_render=1",
|
||||
"view_name": "table",
|
||||
}.items() <= ds_client.ds._can_render_saw.items()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hook_register_output_renderer_renderers_extra_links(ds_client):
|
||||
# https://github.com/simonw/datasette/issues/1179
|
||||
# Links from a .json page previously came out as
|
||||
# /fixtures/facetable.json?_format=testall - which returned JSON
|
||||
response = await ds_client.get("/fixtures/facetable.json?_extra=renderers")
|
||||
assert response.status_code == 200
|
||||
renderers = response.json()["renderers"]
|
||||
assert renderers["json"] == "/fixtures/facetable.json?_extra=renderers&_labels=on"
|
||||
assert (
|
||||
renderers["testall"]
|
||||
== "/fixtures/facetable.testall?_extra=renderers&_labels=on"
|
||||
)
|
||||
saw = ds_client.ds._can_render_saw
|
||||
assert saw["path"] == "/fixtures/facetable"
|
||||
assert saw["full_path"] == "/fixtures/facetable?_extra=renderers"
|
||||
# Following the link should run the renderer
|
||||
response = await ds_client.get(renderers["testall"])
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "text/plain"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hook_register_output_renderer_can_render_canned_query(ds_client):
|
||||
# https://github.com/simonw/datasette/issues/2711
|
||||
|
|
|
|||
|
|
@ -578,6 +578,56 @@ def test_path_with_format_can_override_request_path():
|
|||
assert "/foo.json?x=1" == actual
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,url_vars,expected",
|
||||
[
|
||||
# https://github.com/simonw/datasette/issues/1179
|
||||
("/db/table.Notebook", {"format": "Notebook"}, "/db/table.json"),
|
||||
("/db/table.csv?_size=max", {"format": "csv"}, "/db/table.json?_size=max"),
|
||||
("/db/table", {}, "/db/table.json"),
|
||||
],
|
||||
)
|
||||
def test_path_with_format_replaces_current_format(path, url_vars, expected):
|
||||
request = Request.fake(path, url_vars=url_vars)
|
||||
assert utils.path_with_format(request=request, format="json") == expected
|
||||
|
||||
|
||||
def test_path_with_format_replaces_current_format_with_path_override():
|
||||
request = Request.fake("/prefix/db/table.csv?x=1", url_vars={"format": "csv"})
|
||||
actual = utils.path_with_format(
|
||||
request=request, path="/db/table.csv", format="json"
|
||||
)
|
||||
assert actual == "/db/table.json?x=1"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,url_vars,expected_path,expected_full_path",
|
||||
[
|
||||
("/db/table", {}, "/db/table", "/db/table"),
|
||||
("/db/table.json", {"format": "json"}, "/db/table", "/db/table"),
|
||||
(
|
||||
"/db/table.Notebook?_sort=pk",
|
||||
{"format": "Notebook"},
|
||||
"/db/table",
|
||||
"/db/table?_sort=pk",
|
||||
),
|
||||
(
|
||||
"/prefix/db/-/query.csv?sql=select+1",
|
||||
{"format": "csv"},
|
||||
"/prefix/db/-/query",
|
||||
"/prefix/db/-/query?sql=select+1",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_path_without_format(path, url_vars, expected_path, expected_full_path):
|
||||
request = Request.fake(path, url_vars=url_vars)
|
||||
assert utils.path_without_format(request) == expected_path
|
||||
assert (
|
||||
utils.path_without_format(request, include_query_string=True)
|
||||
== expected_full_path
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bytes,expected",
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue