mirror of
https://github.com/simonw/datasette.git
synced 2026-09-24 10:54:08 +02:00
New .insertDialog() JavaScript method
Anywhere in Datasette can now open an insert dialog pre-populated with suggested data. Using it in one place already, on the row page to provide an insert button for related rows.
This commit is contained in:
parent
f3fa6ecf61
commit
76e6a3bc38
13 changed files with 791 additions and 52 deletions
|
|
@ -700,7 +700,7 @@ def test_config_force_https_urls():
|
|||
("/fixtures/-/query.json?sql=select+blah", 400),
|
||||
# Write APIs
|
||||
("/fixtures/-/create", 405),
|
||||
("/fixtures/facetable/-/insert", 405),
|
||||
("/fixtures/facetable/-/insert", 403),
|
||||
("/fixtures/facetable/-/drop", 405),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1595,7 +1595,6 @@ async def test_create_table_error_rows_twice_with_duplicates(ds_write):
|
|||
(
|
||||
"/data/-/create",
|
||||
"/data/docs/-/drop",
|
||||
"/data/docs/-/insert",
|
||||
),
|
||||
)
|
||||
async def test_method_not_allowed(ds_write, path):
|
||||
|
|
|
|||
|
|
@ -90,20 +90,29 @@ def write_playwright_database(db_path):
|
|||
conn = sqlite3.connect(db_path)
|
||||
try:
|
||||
conn.executescript("""
|
||||
create table licenses (
|
||||
key text primary key,
|
||||
name text
|
||||
);
|
||||
insert into licenses (key, name) values
|
||||
('mit', 'MIT License'),
|
||||
('apache-2.0', 'Apache License 2.0');
|
||||
create table projects (
|
||||
id integer primary key,
|
||||
title text not null,
|
||||
metadata text,
|
||||
logo text,
|
||||
notes text,
|
||||
license text references licenses(key),
|
||||
score integer default 5
|
||||
);
|
||||
insert into projects (title, metadata, logo, notes, score) values
|
||||
insert into projects (title, metadata, logo, notes, license, score) values
|
||||
(
|
||||
'Build Datasette',
|
||||
'{"ok": true}',
|
||||
'asset-original',
|
||||
'Initial notes',
|
||||
'mit',
|
||||
5
|
||||
);
|
||||
""")
|
||||
|
|
@ -347,6 +356,75 @@ def test_insert_row_flow_uses_custom_column_field(page, datasette_server):
|
|||
assert data["score"] == 5
|
||||
|
||||
|
||||
@pytest.mark.playwright
|
||||
def test_insert_dialog_javascript_api_returns_inserted_row(page, datasette_server):
|
||||
page.goto(datasette_server)
|
||||
page.wait_for_function("window.datasette && window.datasette.insertDialog")
|
||||
assert not page.locator('script[src*="edit-tools.js"]').count()
|
||||
|
||||
page.evaluate("""
|
||||
() => {
|
||||
window.insertDialogResult = window.datasette.insertDialog(
|
||||
"data",
|
||||
"projects",
|
||||
{
|
||||
title: "Suggested from JavaScript",
|
||||
metadata: '{"suggested": true}',
|
||||
notes: "Created through insertDialog()",
|
||||
license: "apache-2.0",
|
||||
score: 7
|
||||
},
|
||||
"Review this suggested project before inserting it."
|
||||
);
|
||||
}
|
||||
""")
|
||||
|
||||
dialog = page.locator("#row-edit-dialog")
|
||||
dialog.wait_for()
|
||||
assert "Insert row into projects" in dialog.locator("#row-edit-title").inner_text()
|
||||
assert (
|
||||
dialog.locator("#row-edit-summary").inner_text()
|
||||
== "Review this suggested project before inserting it."
|
||||
)
|
||||
assert dialog.locator('input[name="title"]').input_value() == (
|
||||
"Suggested from JavaScript"
|
||||
)
|
||||
assert dialog.locator('textarea[name="metadata"]').input_value() == (
|
||||
'{"suggested": true}'
|
||||
)
|
||||
assert dialog.locator('textarea[name="notes"]').input_value() == (
|
||||
"Created through insertDialog()"
|
||||
)
|
||||
assert page.evaluate("() => !!customElements.get('datasette-autocomplete')")
|
||||
assert (
|
||||
dialog.locator('datasette-autocomplete input[name="license"]').input_value()
|
||||
== "apache-2.0"
|
||||
)
|
||||
assert dialog.locator('input[name="score"]').input_value() == "7"
|
||||
|
||||
dialog.locator(".row-edit-save").click()
|
||||
result = page.evaluate("() => window.insertDialogResult")
|
||||
assert result["ok"] is True
|
||||
assert result["status"] == "inserted"
|
||||
assert result["database"] == "data"
|
||||
assert result["table"] == "projects"
|
||||
assert result["row_id"] == "2"
|
||||
assert result["row_path"] == "2"
|
||||
assert result["row_url"].endswith("/data/projects/2")
|
||||
assert result["row"]["title"] == "Suggested from JavaScript"
|
||||
assert result["row"]["metadata"] == '{"suggested": true}'
|
||||
assert result["row"]["notes"] == "Created through insertDialog()"
|
||||
assert result["row"]["license"] == "apache-2.0"
|
||||
assert result["row"]["score"] == 7
|
||||
|
||||
data = project_row(datasette_server, 2)
|
||||
assert data["title"] == "Suggested from JavaScript"
|
||||
assert data["metadata"] == '{"suggested": true}'
|
||||
assert data["notes"] == "Created through insertDialog()"
|
||||
assert data["license"] == "apache-2.0"
|
||||
assert data["score"] == 7
|
||||
|
||||
|
||||
@pytest.mark.playwright
|
||||
def test_edit_row_flow_validates_json_and_saves_changes(page, datasette_server):
|
||||
page.goto(f"{datasette_server}data/projects")
|
||||
|
|
|
|||
|
|
@ -1242,6 +1242,76 @@ async def test_table_data_includes_foreign_key_autocomplete_urls():
|
|||
ds.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_row_foreign_key_tables_include_insert_dialog_button():
|
||||
import json
|
||||
|
||||
ds = Datasette(
|
||||
[],
|
||||
config={
|
||||
"databases": {
|
||||
"data": {
|
||||
"tables": {
|
||||
"repos": {
|
||||
"permissions": {
|
||||
"insert-row": {"id": "root"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
try:
|
||||
db = ds.add_database(
|
||||
Database(ds, memory_name="test_row_fk_insert_dialog_button"),
|
||||
name="data",
|
||||
)
|
||||
await db.execute_write_script("""
|
||||
create table licenses (
|
||||
key text primary key,
|
||||
name text
|
||||
);
|
||||
create table repos (
|
||||
id integer primary key,
|
||||
name text,
|
||||
license text references licenses(key)
|
||||
);
|
||||
insert into licenses (key, name)
|
||||
values ('apache-2.0', 'Apache License 2.0');
|
||||
insert into repos (name, license)
|
||||
values ('datasette', 'apache-2.0');
|
||||
""")
|
||||
|
||||
response = await ds.client.get(
|
||||
"/data/licenses/apache-2~2E0", actor={"id": "root"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
soup = Soup(response.text, "html.parser")
|
||||
button = soup.select_one('button[data-insert-dialog][data-table="repos"]')
|
||||
assert button is not None
|
||||
assert button.text.strip() == "Insert"
|
||||
assert button["class"] == ["core", "row-foreign-key-insert"]
|
||||
assert button.has_attr("data-insert-dialog-reload")
|
||||
assert button["data-database"] == "data"
|
||||
assert json.loads(button["data-row"]) == {"license": "apache-2.0"}
|
||||
assert (
|
||||
button["data-message"]
|
||||
== "Insert a row in repos with license set to apache-2.0."
|
||||
)
|
||||
assert not any(
|
||||
"edit-tools.js" in (script.get("src") or "")
|
||||
for script in soup.find_all("script")
|
||||
)
|
||||
|
||||
response = await ds.client.get("/data/licenses/apache-2~2E0")
|
||||
assert response.status_code == 200
|
||||
soup = Soup(response.text, "html.parser")
|
||||
assert soup.select_one("button[data-insert-dialog]") is None
|
||||
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")
|
||||
|
|
@ -1384,6 +1454,48 @@ async def test_row_delete_redirect_to_table_sets_message():
|
|||
ds.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_table_insert_sets_message():
|
||||
ds = Datasette(
|
||||
[],
|
||||
config={
|
||||
"databases": {
|
||||
"data": {
|
||||
"tables": {
|
||||
"items": {
|
||||
"permissions": {
|
||||
"insert-row": {"id": "root"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
try:
|
||||
db = ds.add_database(
|
||||
Database(ds, memory_name="test_table_insert_message"), name="data"
|
||||
)
|
||||
await db.execute_write_script("""
|
||||
create table items (id integer primary key, name text);
|
||||
""")
|
||||
response = await ds.client.post(
|
||||
"/data/items/-/insert?_message=1",
|
||||
actor={"id": "root"},
|
||||
json={"row": {"name": "One"}},
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert response.json() == {
|
||||
"ok": True,
|
||||
"rows": [{"id": 1, "name": "One"}],
|
||||
}
|
||||
assert ds.unsign(response.cookies["ds_messages"], "messages") == [
|
||||
["Inserted row 1 (One)", ds.INFO]
|
||||
]
|
||||
finally:
|
||||
ds.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_row_update_sets_message():
|
||||
ds = Datasette(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue