render_cell(value) plugin hook

Still needs performance testing before I merge this into master
This commit is contained in:
Simon Willison 2018-07-30 08:55:26 -07:00
commit 510e01f224
No known key found for this signature in database
GPG key ID: 17E2DEA2588B7F52
5 changed files with 111 additions and 7 deletions

View file

@ -28,3 +28,8 @@ def extra_js_urls():
@hookspec @hookspec
def publish_subcommand(publish): def publish_subcommand(publish):
"Subcommands for 'datasette publish'" "Subcommands for 'datasette publish'"
@hookspec(firstresult=True)
def render_cell(value):
"Customize rendering of HTML table cell values"

View file

@ -493,14 +493,20 @@ class BaseView(RenderMixin):
display_row = [] display_row = []
for value in row: for value in row:
display_value = value display_value = value
if value in ("", None): # Let the plugins have a go
display_value = jinja2.Markup(" ") from datasette.app import pm
elif is_url(str(value).strip()): plugin_value = pm.hook.render_cell(value=value)
display_value = jinja2.Markup( if plugin_value is not None:
'<a href="{url}">{url}</a>'.format( display_value = plugin_value
url=jinja2.escape(value.strip()) else:
if value in ("", None):
display_value = jinja2.Markup("&nbsp;")
elif is_url(str(display_value).strip()):
display_value = jinja2.Markup(
'<a href="{url}">{url}</a>'.format(
url=jinja2.escape(value.strip())
)
) )
)
display_row.append(display_value) display_row.append(display_value)
display_rows.append(display_row) display_rows.append(display_row)
return { return {

View file

@ -267,3 +267,52 @@ command. Datasette uses this hook internally to implement the default ``now``
and ``heroku`` subcommands, so you can read and ``heroku`` subcommands, so you can read
`their source <https://github.com/simonw/datasette/tree/master/datasette/publish>`_ `their source <https://github.com/simonw/datasette/tree/master/datasette/publish>`_
to see examples of this hook in action. to see examples of this hook in action.
render_cell(value)
~~~~~~~~~~~~~~~~~~
Lets you customize the display of values within table cells in the HTML table view.
``value`` is the value that was loaded from the database.
If your hook returns ``None``, it will be ignored. Use this to indicate that your hook is not able to custom render this particular value.
If the hook returns a string, that string will be rendered in the table cell.
If you want to return HTML markup you can do so by returning a ``jinja2.Markup`` object.
Here is an example of a custom ``render_cell()`` plugin which looks for values that are a JSON string matching the following format::
{"href": "https://www.example.com/", "label": "Name"}
If the value matches that pattern, the plugin returns an HTML link element:
.. code-block:: python
from datasette import hookimpl
import jinja2
import json
@hookimpl
def render_cell(value):
# Render {"href": "...", "label": "..."} as link
stripped = value.strip()
if not stripped.startswith("{") and stripped.endswith("}"):
return None
try:
data = json.loads(value)
except ValueError:
return None
if set(data.keys()) != {"href", "label"}:
return None
href = data["href"]
if not (
href.startswith("/") or href.startswith("http://")
or href.startswith("https://")
):
return None
return jinja2.Markup('<a href="{href}">{label}</a>'.format(
href=jinja2.escape(data["href"]),
label=jinja2.escape(data["label"] or "") or "&nbsp;"
))

View file

@ -208,6 +208,8 @@ def extra_js_urls():
PLUGIN2 = ''' PLUGIN2 = '''
from datasette import hookimpl from datasette import hookimpl
import jinja2
import json
@hookimpl @hookimpl
@ -216,6 +218,30 @@ def extra_js_urls():
'url': 'https://example.com/jquery.js', 'url': 'https://example.com/jquery.js',
'sri': 'SRIHASH', 'sri': 'SRIHASH',
}, 'https://example.com/plugin2.js'] }, 'https://example.com/plugin2.js']
@hookimpl
def render_cell(value):
# Render {"href": "...", "label": "..."} as link
stripped = value.strip()
if not stripped.startswith("{") and stripped.endswith("}"):
return None
try:
data = json.loads(value)
except ValueError:
return None
if set(data.keys()) != {"href", "label"}:
return None
href = data["href"]
if not (
href.startswith("/") or href.startswith("http://")
or href.startswith("https://")
):
return None
return jinja2.Markup('<a href="{href}">{label}</a>'.format(
href=jinja2.escape(data["href"]),
label=jinja2.escape(data["label"] or "") or "&nbsp;"
))
''' '''
TABLES = ''' TABLES = '''

View file

@ -3,6 +3,7 @@ from .fixtures import ( # noqa
app_client, app_client,
) )
import pytest import pytest
import urllib
def test_plugins_dir_plugin(app_client): def test_plugins_dir_plugin(app_client):
@ -67,3 +68,20 @@ def test_plugins_with_duplicate_js_urls(app_client):
) < srcs.index( ) < srcs.index(
'https://example.com/plugin2.js' 'https://example.com/plugin2.js'
) )
def test_plugins_render_cell(app_client):
sql = """
select '{"href": "http://example.com/", "label":"Example"}'
""".strip()
path = "/fixtures?" + urllib.parse.urlencode({
"sql": sql,
})
response = app_client.get(path)
td = Soup(
response.body, "html.parser"
).find("table").find("tbody").find("td")
a = td.find("a")
assert a is not None, str(a)
assert a.attrs["href"] == "http://example.com/"
assert a.text == "Example"