?_extra=render_cells for tables, refs #2619

This commit is contained in:
Simon Willison 2025-12-21 19:52:49 -08:00
commit 97496d5a67
2 changed files with 93 additions and 0 deletions

View file

@ -1492,6 +1492,36 @@ 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():
"Rendered HTML for each cell using the render_cell plugin hook"
columns = [col[0] for col in results.description]
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_name,
database=database_name,
datasette=datasette,
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)
return rendered_rows
async def extra_query():
"Details of the underlying SQL query"
return {
@ -1678,6 +1708,7 @@ async def table_view_data(
run_display_columns_and_rows,
extra_display_columns,
extra_display_rows,
extra_render_cells,
extra_debug,
extra_request,
extra_query,

View file

@ -1383,3 +1383,65 @@ async def test_table_extras(ds_client, extra, expected_json):
)
assert response.status_code == 200
assert response.json() == expected_json
@pytest.mark.asyncio
async def test_extra_render_cells():
"""Test that _extra=render_cells returns rendered HTML from render_cell plugin hook"""
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")
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')")
await db.execute_write("insert into test_render values (2, 'Bob')")
# Register our test plugin
ds.pm.register(TestRenderCellPlugin(), name="TestRenderCellPlugin")
try:
# Request with _extra=render_cells
response = await ds.client.get("/test/test_render.json?_extra=render_cells")
assert response.status_code == 200
data = response.json()
# Verify the response structure
assert "render_cells" 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
# 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>"
# The 'id' column should use default rendering (just the value as string)
assert render_cells[0]["id"] == "1"
# Second row: id=2, name='Bob'
assert render_cells[1]["name"] == "<strong>Bob</strong>"
assert render_cells[1]["id"] == "2"
# The regular rows should still contain raw values
assert data["rows"] == [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
]
finally:
ds.pm.unregister(name="TestRenderCellPlugin")