Support multi-column label_column config, plus a runtime API and UI for setting it

- label_column config can now be a string or an ordered list of columns,
  joined with a space to build a row's display label. Database.label_column_for_table()
  is renamed to label_columns_for_table() and always returns a list.
- Adds a label_columns internal DB table, get/set/remove_label_columns()
  methods, and a set-label-columns permission so the label can be overridden
  at runtime independently of config (config only seeds the value the first
  time a table is seen).
- Adds a POST /<database>/<table>/-/set-label-columns JSON endpoint, modeled
  on the existing set-column-type endpoint.
- Adds a "Set label column(s)" table action and modal for calling that
  endpoint from the table page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019oYhPa7o24kd8Cwx3i4GiD
This commit is contained in:
Claude 2026-07-01 01:05:28 +00:00
commit 8592ce2ed4
No known key found for this signature in database
20 changed files with 1437 additions and 113 deletions

View file

@ -50,7 +50,7 @@ The one exception is the "root" account, which you can sign into while using Dat
The ``--root`` flag is designed for local development and testing. When you start Datasette with ``--root``, the root user automatically receives every permission, including:
* All view permissions (``view-instance``, ``view-database``, ``view-table``, etc.)
* All write permissions (``insert-row``, ``update-row``, ``delete-row``, ``create-table``, ``alter-table``, ``set-column-type``, ``drop-table``)
* All write permissions (``insert-row``, ``update-row``, ``delete-row``, ``create-table``, ``alter-table``, ``set-column-type``, ``set-label-columns``, ``drop-table``)
* Debug permissions (``permissions-debug``, ``debug-menu``)
* Any custom permissions defined by plugins
@ -903,7 +903,7 @@ To grant ``create-table`` to the user with ``id`` of ``editor`` for the ``docs``
}
.. [[[end]]]
Other table-scoped write permissions, including ``set-column-type``, can be configured in the same place.
Other table-scoped write permissions, including ``set-column-type`` and ``set-label-columns``, can be configured in the same place.
And for ``insert-row`` against the ``reports`` table in that ``docs`` database:
@ -1408,6 +1408,18 @@ set-column-type
Actor is allowed to set assigned :ref:`column types <table_configuration_column_types>` for columns in a table.
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)
``table`` is the name of the table (string)
.. _actions_set_label_columns:
set-label-columns
-----------------
Actor is allowed to set the :ref:`label column(s) <table_configuration_label_column>` used to build a display label for rows in a table.
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)

View file

@ -936,6 +936,50 @@ You can override this automatic detection by specifying which column should be u
}
.. [[[end]]]
``label_column`` can also be set to a list of columns, in which case the values of those columns will be joined with a space to build the label:
.. [[[cog
config_example(cog, textwrap.dedent(
"""
databases:
mydatabase:
tables:
example_table:
label_column: [first_name, last_name]
""").strip()
)
.. ]]]
.. tab:: datasette.yaml
.. code-block:: yaml
databases:
mydatabase:
tables:
example_table:
label_column: [first_name, last_name]
.. tab:: datasette.json
.. code-block:: json
{
"databases": {
"mydatabase": {
"tables": {
"example_table": {
"label_column": [
"first_name",
"last_name"
]
}
}
}
}
}
.. [[[end]]]
.. _table_configuration_hidden:
``hidden``

View file

@ -2228,8 +2228,8 @@ The ``Database`` class also provides properties and methods for introspecting th
``await db.fts_table(table)`` - string or None
The name of the FTS table associated with this table, if one exists.
``await db.label_column_for_table(table)`` - string or None
The label column that is associated with this table - either automatically detected or using the ``"label_column"`` key in configuration, see :ref:`table_configuration_label_column`.
``await db.label_columns_for_table(table)`` - list of strings
The label column(s) associated with this table - either automatically detected, set using the ``"label_column"`` key in configuration, or configured at runtime via the ``set-label-columns`` API, see :ref:`table_configuration_label_column`. Returns an empty list if no label column could be determined.
``await db.foreign_keys_for_table(table)`` - list of dictionaries
Details of columns in this table which are foreign keys to other tables. A list of dictionaries where each dictionary is shaped like this: ``{"column": string, "other_table": string, "other_column": string}``.

View file

@ -738,11 +738,11 @@ The available table extras are listed below.
false
``expandable_columns``
List of foreign key columns that can be expanded with labels. Each item is a ``(foreign_key, label_column)`` pair where ``foreign_key`` is the SQLite foreign key dictionary and ``label_column`` is the label column in the referenced table, or ``None``. (See :ref:`expand_foreign_keys` for how to expand these labels.)
List of foreign key columns that can be expanded with labels. Each item is a ``(foreign_key, label_columns)`` pair where ``foreign_key`` is the SQLite foreign key dictionary and ``label_columns`` is a list of label column(s) in the referenced table, or ``None``. (See :ref:`expand_foreign_keys` for how to expand these labels.)
``GET /fixtures/facetable.json?_extra=expandable_columns``
Each item is a ``[foreign_key, label_column]`` pair: the foreign key relationship, then the column in the other table that would be used as the label for each expanded value.
Each item is a ``[foreign_key, label_columns]`` pair: the foreign key relationship, then the list of column(s) in the other table that would be used as the label for each expanded value.
.. code-block:: json
@ -753,7 +753,7 @@ The available table extras are listed below.
"other_table": "facet_cities",
"other_column": "id"
},
"name"
["name"]
]
]
@ -2426,6 +2426,48 @@ This API stores the assignment in Datasette's internal database, so it can be us
Any errors will return ``{"errors": ["... descriptive message ..."], "ok": false}``, and a ``400`` status code for a bad input or a ``403`` status code for an authentication or permission error.
.. _TableSetLabelColumnsView:
Setting label columns
~~~~~~~~~~~~~~~~~~~~~
To set the label column(s) for a table, make a ``POST`` to ``/<database>/<table>/-/set-label-columns``. This requires the :ref:`actions_set_label_columns` permission.
::
POST /<database>/<table>/-/set-label-columns
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token>
.. code-block:: json
{
"columns": ["first_name", "last_name"]
}
This will return a ``200`` response like this:
.. code-block:: json
{
"ok": true,
"database": "data",
"table": "people",
"columns": ["first_name", "last_name"]
}
To revert to Datasette's automatic label column detection, set ``columns`` to ``null``:
.. code-block:: json
{
"columns": null
}
This API stores the assignment in Datasette's internal database, so it can be used with immutable databases as well as mutable ones. See :ref:`table_configuration_label_column` for more about label columns.
Any errors will return ``{"errors": ["... descriptive message ..."], "ok": false}``, and a ``400`` status code for a bad input or a ``403`` status code for an authentication or permission error.
.. _TableDropView:
Dropping tables