diff --git a/datasette/views/table.py b/datasette/views/table.py index 988d3808..dbca3a18 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -382,6 +382,19 @@ async def _table_alter_ui( return None column_types_map = await datasette.get_column_types(database_name, table_name) + foreign_keys_by_column = {} + for fk in await db.foreign_keys_for_table(table_name): + other_column = fk["other_column"] + if other_column is None and await db.table_exists(fk["other_table"]): + other_pks = await db.primary_keys(fk["other_table"]) + if len(other_pks) == 1: + other_column = other_pks[0] + if other_column is None: + continue + foreign_keys_by_column[fk["column"]] = { + "fk_table": fk["other_table"], + "fk_column": other_column, + } columns = [] for column in await db.table_column_details(table_name): if column.hidden: @@ -397,6 +410,7 @@ async def _table_alter_ui( "default": column.default_value, "has_default": column.default_value is not None, "is_pk": column.name in pks, + "foreign_key": foreign_keys_by_column.get(column.name), "column_type": ( {"type": column_type.name, "config": column_type.config} if column_type is not None @@ -412,6 +426,10 @@ async def _table_alter_ui( "primaryKeys": pks, "columnTypes": ALTER_TABLE_COLUMN_TYPES, "defaultExpressions": list(DEFAULT_EXPR_SQL), + "foreignKeyTargetsPath": "{}/-/foreign-key-targets?table={}".format( + datasette.urls.database(database_name), + urllib.parse.quote(table_name, safe=""), + ), } can_set_column_type = await datasette.allowed( action="set-column-type", diff --git a/datasette/views/table_create_alter.py b/datasette/views/table_create_alter.py index 07f13e9d..fa89aae4 100644 --- a/datasette/views/table_create_alter.py +++ b/datasette/views/table_create_alter.py @@ -867,11 +867,20 @@ class DatabaseForeignKeyTargetsView(BaseView): db = await self.ds.resolve_database(request) database_name = db.name - if not await self.ds.allowed( + table_name = request.args.get("table") + can_create_table = await self.ds.allowed( action="create-table", resource=DatabaseResource(database=database_name), actor=request.actor, - ): + ) + can_alter_table = False + if table_name and await db.table_exists(table_name): + can_alter_table = await self.ds.allowed( + action="alter-table", + resource=TableResource(database=database_name, table=table_name), + actor=request.actor, + ) + if not (can_create_table or can_alter_table): return _error(["Permission denied: need create-table"], 403) hidden_tables = await db.execute_fn( diff --git a/tests/test_api_write.py b/tests/test_api_write.py index ca8a46f5..6e8c4d4b 100644 --- a/tests/test_api_write.py +++ b/tests/test_api_write.py @@ -1252,6 +1252,17 @@ async def test_foreign_key_targets_permission_denied(ds_write): } +@pytest.mark.asyncio +async def test_foreign_key_targets_allowed_for_alter_table(ds_write): + token = write_token(ds_write, permissions=["at"]) + response = await ds_write.client.get( + "/data/-/foreign-key-targets?table=docs", + headers=_headers(token), + ) + assert response.status_code == 200, response.text + assert response.json()["ok"] is True + + @pytest.mark.asyncio async def test_alter_table_permission_denied(ds_write): token = write_token(ds_write, permissions=["ir"]) diff --git a/tests/test_table_html.py b/tests/test_table_html.py index 4c80691c..ba9c03a5 100644 --- a/tests/test_table_html.py +++ b/tests/test_table_html.py @@ -1137,6 +1137,9 @@ async def test_table_alter_action_button_and_data(): assert alter_data["tableName"] == "items" assert alter_data["primaryKeys"] == ["id"] assert alter_data["columnTypes"] == ["text", "integer", "float", "blob"] + assert alter_data["foreignKeyTargetsPath"] == ( + "/data/-/foreign-key-targets?table=items" + ) assert alter_data["defaultExpressions"] == [ "current_timestamp", "current_date", @@ -1158,6 +1161,7 @@ async def test_table_alter_action_button_and_data(): "default": None, "has_default": False, "is_pk": True, + "foreign_key": None, "column_type": None, }, { @@ -1168,6 +1172,7 @@ async def test_table_alter_action_button_and_data(): "default": None, "has_default": False, "is_pk": False, + "foreign_key": None, "column_type": {"type": "textarea", "config": None}, }, { @@ -1178,6 +1183,7 @@ async def test_table_alter_action_button_and_data(): "default": "5", "has_default": True, "is_pk": False, + "foreign_key": None, "column_type": None, }, ]