Add foreign key autocomplete to row forms

Expose single-primary-key foreign key autocomplete URLs in table page metadata and load the autocomplete component when needed.

Enhance insert and edit dialogs to wrap foreign-key inputs with the autocomplete web component, show linked selected-row labels, reserve metadata space, and keep the dropdown as a fixed overlay above modal chrome.

Add an explicit _initial=1 autocomplete mode for empty-field starter suggestions while keeping blank q responses empty by default, with tests for the endpoint and table metadata.
This commit is contained in:
Simon Willison 2026-06-14 07:30:34 -07:00
commit 574290fb23
8 changed files with 474 additions and 20 deletions

View file

@ -53,6 +53,53 @@ async def test_autocomplete_blank_q_returns_no_results():
assert response.status_code == 200
assert response.json() == {"rows": []}
response = await ds.client.get("/autocomplete_blank/people/-/autocomplete")
assert response.status_code == 200
assert response.json() == {"rows": []}
@pytest.mark.asyncio
async def test_autocomplete_initial_returns_latest_rows():
ds = Datasette(memory=True)
db = ds.add_memory_database("autocomplete_initial")
await db.execute_write_script("""
create table people (
id integer primary key,
name text
);
insert into people (id, name) values
(1, 'Alice'),
(2, 'Bob'),
(3, 'Cleo');
""")
response = await ds.client.get(
"/autocomplete_initial/people/-/autocomplete?_initial=1"
)
assert response.status_code == 200
assert response.json() == {
"rows": [
{"pks": {"id": 3}, "label": "Cleo"},
{"pks": {"id": 2}, "label": "Bob"},
{"pks": {"id": 1}, "label": "Alice"},
]
}
response = await ds.client.get(
"/autocomplete_initial/people/-/autocomplete?q=&_initial=1"
)
assert response.status_code == 200
assert response.json() == {
"rows": [
{"pks": {"id": 3}, "label": "Cleo"},
{"pks": {"id": 2}, "label": "Bob"},
{"pks": {"id": 1}, "label": "Alice"},
]
}
@pytest.mark.asyncio
async def test_autocomplete_escapes_like_characters():

View file

@ -1050,6 +1050,61 @@ async def test_table_insert_action_includes_compound_primary_keys():
ds.close()
@pytest.mark.asyncio
async def test_table_data_includes_foreign_key_autocomplete_urls():
ds = Datasette([])
try:
db = ds.add_database(
Database(ds, memory_name="test_table_foreign_key_autocomplete"), name="data"
)
await db.execute_write_script("""
create table authors (
id integer primary key,
name text
);
create table tags (
slug text unique,
name text
);
create table articles (
id integer primary key,
author_id integer references authors(id),
implicit_author_id integer references authors,
tag_slug text references tags(slug),
title text
);
insert into authors (id, name) values (1, 'Ada Lovelace');
insert into tags (slug, name) values ('science', 'Science');
insert into articles (
id,
author_id,
implicit_author_id,
tag_slug,
title
) values (
1,
1,
1,
'science',
'Notes'
);
""")
response = await ds.client.get("/data/articles")
assert response.status_code == 200
soup = Soup(response.text, "html.parser")
table_data = table_data_from_soup(soup)
assert table_data["foreignKeys"] == {
"author_id": "/data/authors/-/autocomplete",
"implicit_author_id": "/data/authors/-/autocomplete",
}
assert any(
"autocomplete.js" in (script.get("src") or "")
for script in soup.find_all("script")
)
finally:
ds.close()
@pytest.mark.asyncio
async def test_table_fragment_endpoint(ds_client):
response = await ds_client.get("/fixtures/simple_primary_key/-/fragment?_row=1")