mirror of
https://github.com/simonw/datasette.git
synced 2026-06-23 01:04:49 +02:00
Unify create and alter table modal controls
Share default value controls between the create and alter table dialogs and expose create-table default expressions to the frontend. Add create-table not-null/default handling and align the shared foreign key picker behavior across both dialogs.
This commit is contained in:
parent
063b04ad83
commit
fa43aba309
5 changed files with 707 additions and 170 deletions
|
|
@ -1883,12 +1883,14 @@ select.table-create-input {
|
|||
}
|
||||
|
||||
.table-create-foreign-key-target option,
|
||||
.table-create-custom-column-type option {
|
||||
.table-create-custom-column-type option,
|
||||
.table-create-default-expr option {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.table-create-foreign-key-target option[value=""],
|
||||
.table-create-custom-column-type option[value=""] {
|
||||
.table-create-custom-column-type option[value=""],
|
||||
.table-create-default-expr option[value=""] {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
|
|
@ -1978,11 +1980,44 @@ select.table-create-input {
|
|||
white-space: normal;
|
||||
}
|
||||
|
||||
.table-create-not-null,
|
||||
.table-create-primary-key,
|
||||
.table-create-foreign-key-field {
|
||||
.table-create-foreign-key-field,
|
||||
.table-create-default-options {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.table-create-default-options,
|
||||
.table-alter-default-options {
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.table-create-default-options > summary,
|
||||
.table-alter-default-options > summary {
|
||||
cursor: pointer;
|
||||
color: var(--accent);
|
||||
font-size: 0.85rem;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.table-create-default-options > summary:focus,
|
||||
.table-alter-default-options > summary:focus {
|
||||
outline: 3px solid rgba(26, 86, 219, 0.12);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.table-create-default-grid,
|
||||
.table-alter-default-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 12px 16px;
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
|
||||
.table-create-detail-check input {
|
||||
flex: 0 0 auto;
|
||||
margin: 0.15rem 0 0;
|
||||
|
|
@ -2221,6 +2256,7 @@ dialog.table-alter-dialog::backdrop {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
.table-alter-fields[hidden],
|
||||
.table-alter-dialog .modal-footer [hidden] {
|
||||
display: none;
|
||||
|
|
@ -2358,12 +2394,14 @@ select.table-alter-input {
|
|||
}
|
||||
|
||||
.table-alter-default-expr option,
|
||||
.table-alter-custom-column-type option {
|
||||
.table-alter-custom-column-type option,
|
||||
.table-alter-foreign-key-target option {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.table-alter-default-expr option[value=""],
|
||||
.table-alter-custom-column-type option[value=""] {
|
||||
.table-alter-custom-column-type option[value=""],
|
||||
.table-alter-foreign-key-target option[value=""] {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
|
|
@ -2410,7 +2448,9 @@ select.table-alter-input {
|
|||
}
|
||||
|
||||
.table-alter-not-null,
|
||||
.table-alter-primary-key {
|
||||
.table-alter-primary-key,
|
||||
.table-alter-foreign-key-field,
|
||||
.table-alter-default-options {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
|
|
@ -2657,6 +2697,10 @@ select.table-alter-input {
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.table-alter-default-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.table-alter-dialog .modal-footer {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
|
|
@ -2878,6 +2922,10 @@ select.table-alter-input {
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.table-create-default-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.table-create-dialog .modal-footer {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -266,6 +266,7 @@ async def _create_table_ui_context(
|
|||
),
|
||||
"databaseName": database_name,
|
||||
"columnTypes": CREATE_TABLE_COLUMN_TYPES,
|
||||
"defaultExpressions": list(DEFAULT_EXPR_SQL),
|
||||
}
|
||||
can_set_column_type = await datasette.allowed(
|
||||
action="set-column-type",
|
||||
|
|
|
|||
|
|
@ -295,6 +295,10 @@ def test_create_table_flow(page, datasette_server):
|
|||
placeholder_select.locator("option:checked").inner_text() == "- custom type -"
|
||||
)
|
||||
assert "table-create-input-placeholder" in placeholder_select.get_attribute("class")
|
||||
assert (
|
||||
dialog.locator(".table-create-column-name").nth(0).get_attribute("placeholder")
|
||||
== "column name"
|
||||
)
|
||||
assert dialog.locator(".table-create-column-main").first.evaluate("""node => {
|
||||
const inputHeight = node.querySelector(
|
||||
".table-create-column-name"
|
||||
|
|
@ -306,6 +310,22 @@ def test_create_table_flow(page, datasette_server):
|
|||
}""")
|
||||
dialog.locator('input[name="table"]').fill("playwright_created")
|
||||
dialog.locator(".table-create-column-name").nth(1).fill("title")
|
||||
dialog.locator(".table-create-more-options").nth(1).click()
|
||||
dialog.locator(".table-create-not-null-input").nth(1).check()
|
||||
title_defaults = dialog.locator(".table-create-default-options").nth(1)
|
||||
assert title_defaults.locator("summary").inner_text() == "Set a default value"
|
||||
title_defaults.locator("summary").click()
|
||||
assert "or default to a specific value" in title_defaults.inner_text()
|
||||
title_default_expr = title_defaults.locator(".table-create-default-expr")
|
||||
title_default_input = title_defaults.locator(".table-create-default")
|
||||
assert (
|
||||
"Current timestamp in UTC, e.g. 2026-05-01 13:34:00"
|
||||
in title_default_expr.locator("option").nth(1).inner_text()
|
||||
)
|
||||
title_default_expr.select_option("current_timestamp")
|
||||
assert title_default_input.is_enabled()
|
||||
title_default_input.fill("Untitled")
|
||||
assert title_default_expr.input_value() == ""
|
||||
dialog.locator(".table-create-add-column").click()
|
||||
dialog.locator(".table-create-column-name").nth(2).fill("score")
|
||||
dialog.locator(".table-create-column-type").nth(2).select_option("integer")
|
||||
|
|
@ -337,6 +357,63 @@ def test_create_table_flow(page, datasette_server):
|
|||
assert data["column_types"] == {
|
||||
"metadata": {"type": "json", "config": None},
|
||||
}
|
||||
schema_response = httpx.get(
|
||||
f"{datasette_server}data/-/query.json",
|
||||
params={
|
||||
"sql": (
|
||||
"select sql from sqlite_master where type = 'table' "
|
||||
"and name = 'playwright_created'"
|
||||
)
|
||||
},
|
||||
)
|
||||
schema_response.raise_for_status()
|
||||
schema = schema_response.json()["rows"][0]["sql"]
|
||||
assert "title" in schema
|
||||
assert "NOT NULL DEFAULT 'Untitled'" in schema
|
||||
|
||||
|
||||
@pytest.mark.playwright
|
||||
def test_create_table_foreign_key_selection_updates_column_type(page, datasette_server):
|
||||
page.goto(f"{datasette_server}data")
|
||||
page.locator("details.actions-menu-links summary").click()
|
||||
page.locator('button[data-database-action="create-table"]').click()
|
||||
|
||||
dialog = page.locator("#table-create-dialog")
|
||||
dialog.wait_for()
|
||||
dialog.locator(".table-create-more-options").nth(1).click()
|
||||
|
||||
column_name = dialog.locator(".table-create-column-name").nth(1)
|
||||
type_select = dialog.locator(".table-create-column-type").nth(1)
|
||||
foreign_key_select = dialog.locator(".table-create-foreign-key-target").nth(1)
|
||||
assert column_name.input_value() == ""
|
||||
assert type_select.input_value() == "text"
|
||||
|
||||
foreign_key_select.select_option("projects\u001fid")
|
||||
assert column_name.input_value() == "projects_id"
|
||||
assert type_select.input_value() == "integer"
|
||||
assert foreign_key_select.input_value() == "projects\u001fid"
|
||||
|
||||
|
||||
@pytest.mark.playwright
|
||||
def test_alter_table_foreign_key_selection_updates_blank_column(page, datasette_server):
|
||||
page.goto(f"{datasette_server}data/projects")
|
||||
page.locator("details.actions-menu-links summary").click()
|
||||
page.locator('button[data-table-action="alter-table"]').click()
|
||||
|
||||
dialog = page.locator("#table-alter-dialog")
|
||||
dialog.wait_for()
|
||||
dialog.locator(".table-alter-add-column").click()
|
||||
|
||||
column_name = dialog.locator(".table-alter-column-name").last
|
||||
type_select = dialog.locator(".table-alter-column-type").last
|
||||
foreign_key_select = dialog.locator(".table-alter-foreign-key-target").last
|
||||
assert column_name.input_value() == ""
|
||||
assert type_select.input_value() == "text"
|
||||
|
||||
foreign_key_select.select_option("projects\u001fid")
|
||||
assert column_name.input_value() == "projects_id"
|
||||
assert type_select.input_value() == "integer"
|
||||
assert foreign_key_select.input_value() == "projects\u001fid"
|
||||
|
||||
|
||||
@pytest.mark.playwright
|
||||
|
|
@ -349,6 +426,10 @@ def test_alter_table_flow(page, datasette_server):
|
|||
dialog.wait_for()
|
||||
assert dialog.locator(".modal-title").inner_text() == "Alter table projects"
|
||||
assert dialog.locator(".table-alter-save").is_disabled()
|
||||
assert (
|
||||
dialog.locator(".table-alter-column-name").first.get_attribute("placeholder")
|
||||
== "column name"
|
||||
)
|
||||
assert dialog.locator(".table-alter-column-main").first.evaluate("""node => {
|
||||
const inputHeight = node.querySelector(
|
||||
".table-alter-column-name"
|
||||
|
|
@ -377,15 +458,29 @@ def test_alter_table_flow(page, datasette_server):
|
|||
)
|
||||
assert "Not null" in expanded_options_text
|
||||
assert "This value cannot be left unset" in expanded_options_text
|
||||
assert "Default value" in expanded_options_text
|
||||
assert "or default to a specific time" in expanded_options_text
|
||||
assert "Set a default value" in expanded_options_text
|
||||
assert "Primary key" in expanded_options_text
|
||||
assert "This ID uniquely identifies the record" in expanded_options_text
|
||||
assert "Foreign key" in expanded_options_text
|
||||
first_defaults = dialog.locator(".table-alter-default-options").first
|
||||
first_defaults.locator("summary").click()
|
||||
assert "or default to a specific value" in first_defaults.inner_text()
|
||||
first_default_expr = first_defaults.locator(".table-alter-default-expr")
|
||||
first_default_input = first_defaults.locator(".table-alter-default")
|
||||
assert (
|
||||
"Current timestamp in UTC, e.g. 2026-05-01 13:34:00"
|
||||
in first_default_expr.locator("option").nth(1).inner_text()
|
||||
)
|
||||
first_default_expr.select_option("current_timestamp")
|
||||
assert first_default_input.is_enabled()
|
||||
first_default_input.fill("manual")
|
||||
assert first_default_expr.input_value() == ""
|
||||
|
||||
dialog.locator(".table-alter-add-column").click()
|
||||
assert dialog.locator(".table-alter-save").is_enabled()
|
||||
dialog.locator(".table-alter-column-name").last.fill("status")
|
||||
dialog.locator(".table-alter-column-type").last.select_option("text")
|
||||
dialog.locator(".table-alter-default-options").last.locator("summary").click()
|
||||
dialog.locator(".table-alter-default").last.fill("planned")
|
||||
dialog.locator(".table-alter-save").click()
|
||||
review = dialog.locator(".table-alter-review")
|
||||
|
|
|
|||
|
|
@ -997,6 +997,11 @@ async def test_database_create_table_action_button_and_data():
|
|||
"foreignKeyTargetsPath": "/data/-/foreign-key-targets",
|
||||
"databaseName": "data",
|
||||
"columnTypes": ["text", "integer", "float", "blob"],
|
||||
"defaultExpressions": [
|
||||
"current_timestamp",
|
||||
"current_date",
|
||||
"current_time",
|
||||
],
|
||||
},
|
||||
}
|
||||
assert "customColumnTypes" not in database_data_from_soup(soup)["createTable"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue