From 3aea678eabdf84493557472cdb5461662bd6be3f Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 3 Jul 2026 12:27:02 -0700 Subject: [PATCH] Show auto primary keys in bulk insert preview Render omitted auto integer primary key values as muted auto text in the bulk insert preview so generated IDs are easier to understand. Expose the auto primary key flag in insert UI metadata and cover the preview behavior in Playwright. --- datasette/static/app.css | 3 ++- datasette/static/edit-tools.js | 26 +++++++++++++++++++++++--- datasette/views/table.py | 3 ++- docs/template_context.rst | 2 +- tests/test_playwright.py | 1 + 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/datasette/static/app.css b/datasette/static/app.css index d24a7831..e67be340 100644 --- a/datasette/static/app.css +++ b/datasette/static/app.css @@ -1979,7 +1979,8 @@ textarea.row-edit-input { border-right: none; } -.row-edit-bulk-preview-null { +.row-edit-bulk-preview-null, +.row-edit-bulk-preview-auto { color: var(--muted); font-style: italic; } diff --git a/datasette/static/edit-tools.js b/datasette/static/edit-tools.js index 2712ffe7..7e02ac0f 100644 --- a/datasette/static/edit-tools.js +++ b/datasette/static/edit-tools.js @@ -6038,6 +6038,25 @@ function bulkInsertPreviewValue(value) { return String(value); } +function bulkInsertPreviewCell(column, hasValue, value) { + if (!hasValue && column.is_auto_pk) { + return { + text: "auto", + className: "row-edit-bulk-preview-auto", + }; + } + if (value === null) { + return { + text: bulkInsertPreviewValue(value), + className: "row-edit-bulk-preview-null", + }; + } + return { + text: hasValue ? bulkInsertPreviewValue(value) : "", + className: "", + }; +} + function renderBulkInsertPreview(state, rows) { state.bulkInsertPreview.textContent = ""; var summary = document.createElement("p"); @@ -6068,9 +6087,10 @@ function renderBulkInsertPreview(state, rows) { var td = document.createElement("td"); var hasValue = Object.prototype.hasOwnProperty.call(row, column.name); var value = hasValue ? row[column.name] : ""; - td.textContent = hasValue ? bulkInsertPreviewValue(value) : ""; - if (value === null) { - td.className = "row-edit-bulk-preview-null"; + var cell = bulkInsertPreviewCell(column, hasValue, value); + td.textContent = cell.text; + if (cell.className) { + td.className = cell.className; } tr.appendChild(td); }); diff --git a/datasette/views/table.py b/datasette/views/table.py index caece7fd..b658f61e 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -205,7 +205,7 @@ class TableContext(Context): ) table_insert_ui: dict = field( metadata={ - "help": "Information needed to enable the row insertion UI, or ``None`` if row insertion is not available to the current actor. When present it has ``path``, ``tableName``, ``columns`` and ``primaryKeys`` keys; each column includes ``name``, ``sqlite_type``, ``notnull``, ``default``, ``has_default``, ``is_pk``, ``value_kind`` and ``column_type`` keys." + "help": "Information needed to enable the row insertion UI, or ``None`` if row insertion is not available to the current actor. When present it has ``path``, ``tableName``, ``columns`` and ``primaryKeys`` keys; each column includes ``name``, ``sqlite_type``, ``notnull``, ``default``, ``has_default``, ``is_pk``, ``is_auto_pk``, ``value_kind`` and ``column_type`` keys." } ) table_alter_ui: dict = field( @@ -507,6 +507,7 @@ async def _table_insert_ui( "default": column.default_value, "has_default": column.default_value is not None, "is_pk": is_pk, + "is_auto_pk": is_auto_pk, "value_kind": _column_value_kind_for_insert_form(column), "column_type": ( {"type": column_type.name, "config": column_type.config} diff --git a/docs/template_context.rst b/docs/template_context.rst index ad62995c..89de7293 100644 --- a/docs/template_context.rst +++ b/docs/template_context.rst @@ -389,7 +389,7 @@ Many of these keys are shared with the :ref:`JSON API ` for this page. SQL definition for this table ``table_insert_ui`` - ``dict`` - Information needed to enable the row insertion UI, or ``None`` if row insertion is not available to the current actor. When present it has ``path``, ``tableName``, ``columns`` and ``primaryKeys`` keys; each column includes ``name``, ``sqlite_type``, ``notnull``, ``default``, ``has_default``, ``is_pk``, ``value_kind`` and ``column_type`` keys. + Information needed to enable the row insertion UI, or ``None`` if row insertion is not available to the current actor. When present it has ``path``, ``tableName``, ``columns`` and ``primaryKeys`` keys; each column includes ``name``, ``sqlite_type``, ``notnull``, ``default``, ``has_default``, ``is_pk``, ``is_auto_pk``, ``value_kind`` and ``column_type`` keys. ``table_page_data`` - ``dict`` JSON data used by JavaScript on the table page. Includes ``database``, ``table`` and ``tableUrl``, plus optional ``foreignKeys`` mapping column names to autocomplete URLs, optional ``insertRow`` data and optional ``alterTable`` data. diff --git a/tests/test_playwright.py b/tests/test_playwright.py index 421b86f8..27967d8b 100644 --- a/tests/test_playwright.py +++ b/tests/test_playwright.py @@ -1329,6 +1329,7 @@ def test_bulk_insert_omits_columns_absent_from_pasted_input(page, datasette_serv preview_text = dialog.locator(".row-edit-bulk-preview-table").inner_text() assert "Only title" in preview_text assert "undefined" not in preview_text + assert dialog.locator(".row-edit-bulk-preview-auto").inner_text() == "auto" dialog.locator(".row-edit-save").click() dialog.locator(