mirror of
https://github.com/simonw/datasette.git
synced 2026-05-31 06:07:05 +02:00
Row pages link to foreign keys from table display, closes #1592
https://gisthost.github.io/?40813f5b3e4d83c0efe1c09135f84290/index.html Also now shows primary key column first and in bold on that page.
This commit is contained in:
parent
1263380ea6
commit
97201f067c
2 changed files with 88 additions and 8 deletions
|
|
@ -5,12 +5,14 @@ from datasette.resources import TableResource
|
|||
from .base import DataView, BaseView, _error
|
||||
from datasette.utils import (
|
||||
await_me_maybe,
|
||||
CustomRow,
|
||||
make_slot_function,
|
||||
to_css_class,
|
||||
escape_sqlite,
|
||||
)
|
||||
from datasette.plugins import pm
|
||||
import json
|
||||
import markupsafe
|
||||
import sqlite_utils
|
||||
from .table import display_columns_and_rows, _get_extras
|
||||
|
||||
|
|
@ -42,13 +44,62 @@ class RowView(DataView):
|
|||
if not rows:
|
||||
raise NotFound(f"Record not found: {pk_values}")
|
||||
|
||||
pks = resolved.pks
|
||||
|
||||
async def template_data():
|
||||
# Reorder columns so primary keys come first
|
||||
pk_set = set(pks)
|
||||
pk_cols = [d for d in results.description if d[0] in pk_set]
|
||||
non_pk_cols = [d for d in results.description if d[0] not in pk_set]
|
||||
reordered_description = pk_cols + non_pk_cols
|
||||
reordered_columns = [d[0] for d in reordered_description]
|
||||
|
||||
# Reorder row data to match
|
||||
reordered_rows = []
|
||||
for row in rows:
|
||||
new_row = CustomRow(reordered_columns)
|
||||
for col in reordered_columns:
|
||||
new_row[col] = row[col]
|
||||
reordered_rows.append(new_row)
|
||||
|
||||
# Expand foreign key columns into dicts so display_columns_and_rows
|
||||
# renders them as hyperlinks, matching the table view behavior
|
||||
expanded_rows = reordered_rows
|
||||
for fk in await db.foreign_keys_for_table(table):
|
||||
column = fk["column"]
|
||||
if column not in reordered_columns:
|
||||
continue
|
||||
column_index = reordered_columns.index(column)
|
||||
values = [row[column_index] for row in expanded_rows]
|
||||
expanded_labels = await self.ds.expand_foreign_keys(
|
||||
request.actor, database, table, column, values
|
||||
)
|
||||
if expanded_labels:
|
||||
new_rows = []
|
||||
for row in expanded_rows:
|
||||
new_row = CustomRow(reordered_columns)
|
||||
for col in reordered_columns:
|
||||
value = row[col]
|
||||
if (
|
||||
col == column
|
||||
and (col, value) in expanded_labels
|
||||
and value is not None
|
||||
):
|
||||
new_row[col] = {
|
||||
"value": value,
|
||||
"label": expanded_labels[(col, value)],
|
||||
}
|
||||
else:
|
||||
new_row[col] = value
|
||||
new_rows.append(new_row)
|
||||
expanded_rows = new_rows
|
||||
|
||||
display_columns, display_rows = await display_columns_and_rows(
|
||||
self.ds,
|
||||
database,
|
||||
table,
|
||||
results.description,
|
||||
rows,
|
||||
reordered_description,
|
||||
expanded_rows,
|
||||
link_column=False,
|
||||
truncate_cells=0,
|
||||
request=request,
|
||||
|
|
@ -56,6 +107,14 @@ class RowView(DataView):
|
|||
for column in display_columns:
|
||||
column["sortable"] = False
|
||||
|
||||
# Bold primary key cell values
|
||||
for row in display_rows:
|
||||
for cell in row:
|
||||
if cell["column"] in pk_set:
|
||||
cell["value"] = markupsafe.Markup(
|
||||
"<strong>{}</strong>".format(cell["value"])
|
||||
)
|
||||
|
||||
row_actions = []
|
||||
for hook in pm.hook.row_actions(
|
||||
datasette=self.ds,
|
||||
|
|
@ -71,6 +130,7 @@ class RowView(DataView):
|
|||
|
||||
return {
|
||||
"private": private,
|
||||
"columns": reordered_columns,
|
||||
"foreign_key_tables": await self.foreign_key_tables(
|
||||
database, table, pk_values
|
||||
),
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ async def test_row_html_simple_primary_key(ds_client):
|
|||
assert ["id", "content"] == [th.string.strip() for th in table.select("thead th")]
|
||||
assert [
|
||||
[
|
||||
'<td class="col-id type-int">1</td>',
|
||||
'<td class="col-id type-int"><strong>1</strong></td>',
|
||||
'<td class="col-content type-str">hello</td>',
|
||||
]
|
||||
] == [[str(td) for td in tr.select("td")] for tr in table.select("tbody tr")]
|
||||
|
|
@ -363,7 +363,7 @@ async def test_row_html_no_primary_key(ds_client):
|
|||
]
|
||||
expected = [
|
||||
[
|
||||
'<td class="col-rowid type-int">1</td>',
|
||||
'<td class="col-rowid type-int"><strong>1</strong></td>',
|
||||
'<td class="col-content type-str">1</td>',
|
||||
'<td class="col-a type-str">a1</td>',
|
||||
'<td class="col-b type-str">b1</td>',
|
||||
|
|
@ -406,6 +406,26 @@ async def test_row_links_from_other_tables(
|
|||
assert link == expected_link
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_row_foreign_key_links(ds_client):
|
||||
# Row detail page should render foreign key values as hyperlinks
|
||||
response = await ds_client.get("/fixtures/foreign_key_references/1")
|
||||
assert response.status_code == 200
|
||||
soup = Soup(response.text, "html.parser")
|
||||
# foreign_key_with_label=1 references simple_primary_key(id=1, content="hello")
|
||||
td = soup.find("td", {"class": "col-foreign_key_with_label"})
|
||||
a = td.find("a")
|
||||
assert a is not None, "Expected foreign key value to be a hyperlink"
|
||||
assert a["href"] == "/fixtures/simple_primary_key/1"
|
||||
assert a.text == "hello"
|
||||
# Primary key column should be first and bold
|
||||
table = soup.find("table")
|
||||
headers = [th.text.strip() for th in table.select("thead th")]
|
||||
assert headers[0] == "pk"
|
||||
first_td = table.select("tbody tr td")[0]
|
||||
assert first_td.find("strong") is not None, "PK value should be bold"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected",
|
||||
|
|
@ -414,8 +434,8 @@ async def test_row_links_from_other_tables(
|
|||
"/fixtures/compound_primary_key/a,b",
|
||||
[
|
||||
[
|
||||
'<td class="col-pk1 type-str">a</td>',
|
||||
'<td class="col-pk2 type-str">b</td>',
|
||||
'<td class="col-pk1 type-str"><strong>a</strong></td>',
|
||||
'<td class="col-pk2 type-str"><strong>b</strong></td>',
|
||||
'<td class="col-content type-str">c</td>',
|
||||
]
|
||||
],
|
||||
|
|
@ -424,8 +444,8 @@ async def test_row_links_from_other_tables(
|
|||
"/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd",
|
||||
[
|
||||
[
|
||||
'<td class="col-pk1 type-str">a/b</td>',
|
||||
'<td class="col-pk2 type-str">.c-d</td>',
|
||||
'<td class="col-pk1 type-str"><strong>a/b</strong></td>',
|
||||
'<td class="col-pk2 type-str"><strong>.c-d</strong></td>',
|
||||
'<td class="col-content type-str">c</td>',
|
||||
]
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue