mirror of
https://github.com/simonw/datasette.git
synced 2026-05-27 12:34:37 +02:00
Initial render_cell and foreign_key_tables extras for row
Closes #2619, refs #2050
This commit is contained in:
parent
97496d5a67
commit
eae94dc2c3
4 changed files with 111 additions and 16 deletions
|
|
@ -12,7 +12,7 @@ from datasette.utils import (
|
|||
from datasette.plugins import pm
|
||||
import json
|
||||
import sqlite_utils
|
||||
from .table import display_columns_and_rows
|
||||
from .table import display_columns_and_rows, _get_extras
|
||||
|
||||
|
||||
class RowView(DataView):
|
||||
|
|
@ -104,11 +104,48 @@ class RowView(DataView):
|
|||
"primary_key_values": pk_values,
|
||||
}
|
||||
|
||||
# Handle _extra parameter (new style)
|
||||
extras = _get_extras(request)
|
||||
|
||||
# Also support legacy _extras parameter for backward compatibility
|
||||
if "foreign_key_tables" in (request.args.get("_extras") or "").split(","):
|
||||
extras.add("foreign_key_tables")
|
||||
|
||||
# Process extras
|
||||
if "foreign_key_tables" in extras:
|
||||
data["foreign_key_tables"] = await self.foreign_key_tables(
|
||||
database, table, pk_values
|
||||
)
|
||||
|
||||
if "render_cell" in extras:
|
||||
# Call render_cell plugin hook for each cell
|
||||
rendered_rows = []
|
||||
for row in rows:
|
||||
rendered_row = {}
|
||||
for value, column in zip(row, columns):
|
||||
# Call render_cell plugin hook
|
||||
plugin_display_value = None
|
||||
for candidate in pm.hook.render_cell(
|
||||
row=row,
|
||||
value=value,
|
||||
column=column,
|
||||
table=table,
|
||||
database=database,
|
||||
datasette=self.ds,
|
||||
request=request,
|
||||
):
|
||||
candidate = await await_me_maybe(candidate)
|
||||
if candidate is not None:
|
||||
plugin_display_value = candidate
|
||||
break
|
||||
if plugin_display_value:
|
||||
rendered_row[column] = str(plugin_display_value)
|
||||
else:
|
||||
# Default: convert value to string
|
||||
rendered_row[column] = "" if value is None else str(value)
|
||||
rendered_rows.append(rendered_row)
|
||||
data["render_cell"] = rendered_rows
|
||||
|
||||
return (
|
||||
data,
|
||||
template_data,
|
||||
|
|
|
|||
|
|
@ -1492,7 +1492,7 @@ async def table_view_data(
|
|||
async def extra_display_rows(run_display_columns_and_rows):
|
||||
return run_display_columns_and_rows["rows"]
|
||||
|
||||
async def extra_render_cells():
|
||||
async def extra_render_cell():
|
||||
"Rendered HTML for each cell using the render_cell plugin hook"
|
||||
columns = [col[0] for col in results.description]
|
||||
rendered_rows = []
|
||||
|
|
@ -1708,7 +1708,7 @@ async def table_view_data(
|
|||
run_display_columns_and_rows,
|
||||
extra_display_columns,
|
||||
extra_display_rows,
|
||||
extra_render_cells,
|
||||
extra_render_cell,
|
||||
extra_debug,
|
||||
extra_request,
|
||||
extra_query,
|
||||
|
|
|
|||
|
|
@ -752,6 +752,62 @@ async def test_row_foreign_key_tables(ds_client):
|
|||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_row_extra_render_cell():
|
||||
"""Test that _extra=render_cell returns rendered HTML from render_cell plugin hook on row pages"""
|
||||
from datasette import hookimpl
|
||||
from datasette.app import Datasette
|
||||
|
||||
class TestRenderCellPlugin:
|
||||
__name__ = "TestRenderCellPlugin"
|
||||
|
||||
@hookimpl
|
||||
def render_cell(self, value, column, table, database):
|
||||
# Only modify cells in our test table
|
||||
if table == "test_render" and column == "name":
|
||||
return f"<strong>{value}</strong>"
|
||||
return None
|
||||
|
||||
ds = Datasette(memory=True)
|
||||
await ds.invoke_startup()
|
||||
db = ds.add_memory_database("test_row_render")
|
||||
await db.execute_write(
|
||||
"create table test_render (id integer primary key, name text)"
|
||||
)
|
||||
await db.execute_write("insert into test_render values (1, 'Alice')")
|
||||
|
||||
# Register our test plugin
|
||||
ds.pm.register(TestRenderCellPlugin(), name="TestRenderCellPlugin")
|
||||
|
||||
try:
|
||||
# Request row with _extra=render_cell
|
||||
response = await ds.client.get(
|
||||
"/test_row_render/test_render/1.json?_extra=render_cell"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Verify the response structure
|
||||
assert "render_cell" in data
|
||||
assert "rows" in data
|
||||
|
||||
# render_cell should be a list with one row (since this is a row page)
|
||||
render_cell = data["render_cell"]
|
||||
assert len(render_cell) == 1
|
||||
|
||||
# The row: id=1, name='Alice'
|
||||
# The 'name' column should be rendered by our plugin as <strong>Alice</strong>
|
||||
assert render_cell[0]["name"] == "<strong>Alice</strong>"
|
||||
# The 'id' column should use default rendering (just the value as string)
|
||||
assert render_cell[0]["id"] == "1"
|
||||
|
||||
# The regular rows should still contain raw values
|
||||
assert data["rows"] == [{"id": 1, "name": "Alice"}]
|
||||
|
||||
finally:
|
||||
ds.pm.unregister(name="TestRenderCellPlugin")
|
||||
|
||||
|
||||
def test_databases_json(app_client_two_attached_databases_one_immutable):
|
||||
response = app_client_two_attached_databases_one_immutable.get("/-/databases.json")
|
||||
databases = response.json
|
||||
|
|
|
|||
|
|
@ -1386,8 +1386,8 @@ async def test_table_extras(ds_client, extra, expected_json):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extra_render_cells():
|
||||
"""Test that _extra=render_cells returns rendered HTML from render_cell plugin hook"""
|
||||
async def test_extra_render_cell():
|
||||
"""Test that _extra=render_cell returns rendered HTML from render_cell plugin hook"""
|
||||
from datasette import hookimpl
|
||||
from datasette.app import Datasette
|
||||
|
||||
|
|
@ -1403,7 +1403,7 @@ async def test_extra_render_cells():
|
|||
|
||||
ds = Datasette(memory=True)
|
||||
await ds.invoke_startup()
|
||||
db = ds.add_memory_database("test")
|
||||
db = ds.add_memory_database("test_table_render")
|
||||
await db.execute_write(
|
||||
"create table test_render (id integer primary key, name text)"
|
||||
)
|
||||
|
|
@ -1414,28 +1414,30 @@ async def test_extra_render_cells():
|
|||
ds.pm.register(TestRenderCellPlugin(), name="TestRenderCellPlugin")
|
||||
|
||||
try:
|
||||
# Request with _extra=render_cells
|
||||
response = await ds.client.get("/test/test_render.json?_extra=render_cells")
|
||||
# Request with _extra=render_cell
|
||||
response = await ds.client.get(
|
||||
"/test_table_render/test_render.json?_extra=render_cell"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Verify the response structure
|
||||
assert "render_cells" in data
|
||||
assert "render_cell" in data
|
||||
assert "rows" in data
|
||||
|
||||
# render_cells should be a list of rows, each row being a dict of column -> rendered HTML
|
||||
render_cells = data["render_cells"]
|
||||
assert len(render_cells) == 2
|
||||
# render_cell should be a list of rows, each row being a dict of column -> rendered HTML
|
||||
render_cell = data["render_cell"]
|
||||
assert len(render_cell) == 2
|
||||
|
||||
# First row: id=1, name='Alice'
|
||||
# The 'name' column should be rendered by our plugin as <strong>Alice</strong>
|
||||
assert render_cells[0]["name"] == "<strong>Alice</strong>"
|
||||
assert render_cell[0]["name"] == "<strong>Alice</strong>"
|
||||
# The 'id' column should use default rendering (just the value as string)
|
||||
assert render_cells[0]["id"] == "1"
|
||||
assert render_cell[0]["id"] == "1"
|
||||
|
||||
# Second row: id=2, name='Bob'
|
||||
assert render_cells[1]["name"] == "<strong>Bob</strong>"
|
||||
assert render_cells[1]["id"] == "2"
|
||||
assert render_cell[1]["name"] == "<strong>Bob</strong>"
|
||||
assert render_cell[1]["id"] == "2"
|
||||
|
||||
# The regular rows should still contain raw values
|
||||
assert data["rows"] == [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue