mirror of
https://github.com/simonw/datasette.git
synced 2026-10-18 15:26:57 +02:00
Merge e16e3af4f8 into e1a4e03635
This commit is contained in:
commit
f55764fcd4
3 changed files with 102 additions and 2 deletions
|
|
@ -5,6 +5,7 @@ import markupsafe
|
|||
|
||||
from datasette import hookimpl
|
||||
from datasette.column_types import ColumnType, SQLiteType
|
||||
from datasette.utils import truncate_url
|
||||
|
||||
_HTTP_URL_RE = re.compile(r"https?://\S+", re.IGNORECASE)
|
||||
|
||||
|
|
@ -23,14 +24,17 @@ class UrlColumnType(ColumnType):
|
|||
description = "URL"
|
||||
sqlite_types = (SQLiteType.TEXT,)
|
||||
|
||||
async def render_cell(self, value, column, table, database, datasette, request):
|
||||
async def render_cell(
|
||||
self, value, column, table, database, datasette, request, truncate_cells=0
|
||||
):
|
||||
if not value or not isinstance(value, str):
|
||||
return None
|
||||
normalized = _normalize_http_url(value)
|
||||
if normalized is None:
|
||||
return markupsafe.escape(value.strip())
|
||||
escaped = markupsafe.escape(normalized)
|
||||
return markupsafe.Markup(f'<a href="{escaped}">{escaped}</a>')
|
||||
link_text = markupsafe.escape(truncate_url(normalized, truncate_cells))
|
||||
return markupsafe.Markup(f'<a href="{escaped}">{link_text}</a>')
|
||||
|
||||
async def validate(self, value, datasette):
|
||||
if value is None or value == "":
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import asyncio
|
||||
import inspect
|
||||
import itertools
|
||||
import json
|
||||
import time
|
||||
|
|
@ -666,6 +667,13 @@ async def display_columns_and_rows(
|
|||
|
||||
# Look up column types for this table
|
||||
column_types_map = await datasette.get_column_types(database_name, table_name)
|
||||
# Column types can opt in to this page's truncation length by accepting
|
||||
# a truncate_cells argument to their render_cell() method
|
||||
truncate_cells_columns = {
|
||||
column
|
||||
for column, ct in column_types_map.items()
|
||||
if "truncate_cells" in inspect.signature(ct.render_cell).parameters
|
||||
}
|
||||
|
||||
column_details = {
|
||||
col.name: col for col in await db.table_column_details(table_name)
|
||||
|
|
@ -796,6 +804,9 @@ async def display_columns_and_rows(
|
|||
plugin_display_value = None
|
||||
ct = column_types_map.get(column)
|
||||
if ct:
|
||||
kwargs = {}
|
||||
if column in truncate_cells_columns:
|
||||
kwargs["truncate_cells"] = truncate_cells
|
||||
candidate = await ct.render_cell(
|
||||
value=value,
|
||||
column=column,
|
||||
|
|
@ -803,6 +814,7 @@ async def display_columns_and_rows(
|
|||
database=database_name,
|
||||
datasette=datasette,
|
||||
request=request,
|
||||
**kwargs,
|
||||
)
|
||||
if candidate is not None:
|
||||
plugin_display_value = candidate
|
||||
|
|
|
|||
|
|
@ -914,6 +914,90 @@ async def test_html_table_page_rendering(ds_ct):
|
|||
assert 'href="https://example.com"' in html
|
||||
|
||||
|
||||
# --- url column type link text truncation, refs #1805 ---
|
||||
|
||||
LONG_URL = (
|
||||
"https://images.openfoodfacts.org/images/products/000/000/000/088/"
|
||||
"nutrition_fr.5.200.jpg"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ds_long_url(tmp_path_factory):
|
||||
db_path = str(tmp_path_factory.mktemp("dbs") / "data.db")
|
||||
db = sqlite3.connect(db_path)
|
||||
db.execute("create table posts (id integer primary key, website text)")
|
||||
db.execute("insert into posts values (1, ?)", [LONG_URL])
|
||||
db.execute("insert into posts values (2, 'not a url')")
|
||||
db.commit()
|
||||
db.close()
|
||||
ds = Datasette(
|
||||
[db_path],
|
||||
settings={"truncate_cells_html": 30},
|
||||
config={
|
||||
"databases": {
|
||||
"data": {"tables": {"posts": {"column_types": {"website": "url"}}}}
|
||||
}
|
||||
},
|
||||
)
|
||||
yield ds
|
||||
ds.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"truncate_cells_html,expected_text",
|
||||
(
|
||||
(30, "https://images.openfoodfac….jpg"),
|
||||
# 0 disables truncation
|
||||
(0, LONG_URL),
|
||||
),
|
||||
)
|
||||
async def test_url_column_type_truncates_link_text(
|
||||
ds_long_url, truncate_cells_html, expected_text
|
||||
):
|
||||
# The table page truncates the link text but the href keeps the full URL
|
||||
ds_long_url._settings["truncate_cells_html"] = truncate_cells_html
|
||||
await ds_long_url.invoke_startup()
|
||||
response = await ds_long_url.client.get("/data/posts")
|
||||
assert response.status_code == 200
|
||||
link = Soup(response.text, "html.parser").select_one("td.col-website a")
|
||||
assert link["href"] == LONG_URL
|
||||
assert link.text == expected_text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_url_column_type_full_link_text_on_row_page(ds_long_url):
|
||||
# Row pages show full values, so the link text is not truncated there
|
||||
await ds_long_url.invoke_startup()
|
||||
response = await ds_long_url.client.get("/data/posts/1")
|
||||
assert response.status_code == 200
|
||||
link = Soup(response.text, "html.parser").select_one("td.col-website a")
|
||||
assert link["href"] == LONG_URL
|
||||
assert link.text == LONG_URL
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("path", ("/data/posts.json", "/data/posts/1.json"))
|
||||
async def test_url_column_type_full_link_text_in_json(ds_long_url, path):
|
||||
# truncate_cells_html only applies to the HTML table view
|
||||
await ds_long_url.invoke_startup()
|
||||
response = await ds_long_url.client.get(path + "?_extra=render_cell")
|
||||
assert response.status_code == 200
|
||||
rendered = response.json()["render_cell"][0]["website"]
|
||||
assert rendered == f'<a href="{LONG_URL}">{LONG_URL}</a>'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_url_column_type_non_http_value_unchanged(ds_long_url):
|
||||
await ds_long_url.invoke_startup()
|
||||
response = await ds_long_url.client.get("/data/posts")
|
||||
assert response.status_code == 200
|
||||
td = Soup(response.text, "html.parser").select("td.col-website")[1]
|
||||
assert td.find("a") is None
|
||||
assert td.text == "not a url"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_column_type_ui_data_hidden_without_permission(ds_ct):
|
||||
await ds_ct.invoke_startup()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue