diff --git a/datasette/templates/_rows_and_columns.html b/datasette/templates/_table.html
similarity index 100%
rename from datasette/templates/_rows_and_columns.html
rename to datasette/templates/_table.html
diff --git a/datasette/templates/row.html b/datasette/templates/row.html
index baffaf96..bda1e4e2 100644
--- a/datasette/templates/row.html
+++ b/datasette/templates/row.html
@@ -24,7 +24,7 @@
This data as {% for name, url in renderers.items() %}{{ name }}{{ ", " if not loop.last }}{% endfor %}
-{% include custom_rows_and_columns_templates %}
+{% include custom_table_templates %}
{% if foreign_key_tables %}
Links from other tables
diff --git a/datasette/templates/table.html b/datasette/templates/table.html
index 5ba3ff6d..2287e901 100644
--- a/datasette/templates/table.html
+++ b/datasette/templates/table.html
@@ -145,7 +145,7 @@
{% endif %}
-{% include custom_rows_and_columns_templates %}
+{% include custom_table_templates %}
{% if next_url %}
Next page
diff --git a/datasette/views/table.py b/datasette/views/table.py
index 06be5671..8ba3abe4 100644
--- a/datasette/views/table.py
+++ b/datasette/views/table.py
@@ -33,6 +33,35 @@ LINK_WITH_LABEL = (
LINK_WITH_VALUE = '{id}'
+class Row:
+ def __init__(self, cells):
+ self.cells = cells
+
+ def __iter__(self):
+ return iter(self.cells)
+
+ def __getitem__(self, key):
+ for cell in self.cells:
+ if cell["column"] == key:
+ return cell["raw"]
+ raise KeyError
+
+ def display(self, key):
+ for cell in self.cells:
+ if cell["column"] == key:
+ return cell["value"]
+ return None
+
+ def __str__(self):
+ d = {
+ key: self[key]
+ for key in [
+ c["column"] for c in self.cells if not c.get("is_special_link_column")
+ ]
+ }
+ return json.dumps(d, default=repr, indent=2)
+
+
class RowTableShared(DataView):
async def sortable_columns_for_table(self, database, table, use_rowid):
db = self.ds.databases[database]
@@ -76,18 +105,18 @@ class RowTableShared(DataView):
# Unless we are a view, the first column is a link - either to the rowid
# or to the simple or compound primary key
if link_column:
+ is_special_link_column = len(pks) != 1
+ pk_path = path_from_row_pks(row, pks, not pks, False)
cells.append(
{
"column": pks[0] if len(pks) == 1 else "Link",
+ "is_special_link_column": is_special_link_column,
+ "raw": pk_path,
"value": jinja2.Markup(
'{flat_pks}'.format(
database=database,
table=urllib.parse.quote_plus(table),
- flat_pks=str(
- jinja2.escape(
- path_from_row_pks(row, pks, not pks, False)
- )
- ),
+ flat_pks=str(jinja2.escape(pk_path)),
flat_pks_quoted=path_from_row_pks(row, pks, not pks),
)
),
@@ -159,8 +188,8 @@ class RowTableShared(DataView):
if truncate_cells and len(display_value) > truncate_cells:
display_value = display_value[:truncate_cells] + u"\u2026"
- cells.append({"column": column, "value": display_value})
- cell_rows.append(cells)
+ cells.append({"column": column, "value": display_value, "raw": value})
+ cell_rows.append(Row(cells))
if link_column:
# Add the link column header.
@@ -715,14 +744,14 @@ class TableView(RowTableShared):
"sort": sort,
"sort_desc": sort_desc,
"disable_sort": is_view,
- "custom_rows_and_columns_templates": [
- "_rows_and_columns-{}-{}.html".format(
+ "custom_table_templates": [
+ "_table-{}-{}.html".format(
to_css_class(database), to_css_class(table)
),
- "_rows_and_columns-table-{}-{}.html".format(
+ "_table-table-{}-{}.html".format(
to_css_class(database), to_css_class(table)
),
- "_rows_and_columns.html",
+ "_table.html",
],
"metadata": metadata,
"view_definition": await db.get_view_definition(table),
@@ -799,14 +828,14 @@ class RowView(RowTableShared):
),
"display_columns": display_columns,
"display_rows": display_rows,
- "custom_rows_and_columns_templates": [
- "_rows_and_columns-{}-{}.html".format(
+ "custom_table_templates": [
+ "_table-{}-{}.html".format(
to_css_class(database), to_css_class(table)
),
- "_rows_and_columns-row-{}-{}.html".format(
+ "_table-row-{}-{}.html".format(
to_css_class(database), to_css_class(table)
),
- "_rows_and_columns.html",
+ "_table.html",
],
"metadata": (self.ds.metadata("databases") or {})
.get(database, {})
diff --git a/docs/custom_templates.rst b/docs/custom_templates.rst
index b0863381..47271542 100644
--- a/docs/custom_templates.rst
+++ b/docs/custom_templates.rst
@@ -144,15 +144,15 @@ The lookup rules Datasette uses are as follows::
row-mydatabase-mytable.html
row.html
- Rows and columns include on table page:
- _rows_and_columns-table-mydatabase-mytable.html
- _rows_and_columns-mydatabase-mytable.html
- _rows_and_columns.html
+ Table of rows and columns include on table page:
+ _table-table-mydatabase-mytable.html
+ _table-mydatabase-mytable.html
+ _table.html
- Rows and columns include on row page:
- _rows_and_columns-row-mydatabase-mytable.html
- _rows_and_columns-mydatabase-mytable.html
- _rows_and_columns.html
+ Table of rows and columns include on row page:
+ _table-row-mydatabase-mytable.html
+ _table-mydatabase-mytable.html
+ _table.html
If a table name has spaces or other unexpected characters in it, the template
filename will follow the same rules as our custom ```` CSS classes - for
@@ -189,38 +189,28 @@ content you can do so by creating a ``row.html`` template like this::
Note the ``default:row.html`` template name, which ensures Jinja will inherit
from the default template.
-The ``_rows_and_columns.html`` template is included on both the row and the table
-page, and displays the content of the row. The default ``_rows_and_columns.html`` template
-`can be seen here `_.
+The ``_table.html`` template is included by both the row and the table pages,
+and a list of rows. The default ``_table.html`` template renders them as an
+HTML template and `can be seen here `_.
You can provide a custom template that applies to all of your databases and
tables, or you can provide custom templates for specific tables using the
template naming scheme described above.
-Say for example you want to output a certain column as unescaped HTML. You could
-provide a custom ``_rows_and_columns.html`` template like this::
+If you want to present your data in a format other than an HTML table, you
+can do so by looping through ``display_rows`` in your own ``_table.html``
+template. You can use ``{{ row["column_name"] }}`` to output the raw value
+of a specific column.
-
-
-
- {% for column in display_columns %}
- | {{ column }} |
- {% endfor %}
-
-
-
- {% for row in display_rows %}
-
- {% for cell in row %}
- |
- {% if cell.column == 'description' %}
- {{ cell.value|safe }}
- {% else %}
- {{ cell.value }}
- {% endif %}
- |
- {% endfor %}
-
- {% endfor %}
-
-
+If you want to output the rendered HTML version of a column, including any
+links to foreign keys, you can use ``{{ row.display("column_name") }}``.
+
+Here is an example of a custom ``_table.html`` template::
+
+ {% for row in display_rows %}
+
+
{{ row["title"] }}
+
{{ row["description"] }}
+ Category: {{ row.display("category_id") }}
+
+ {% endfor %}
diff --git a/tests/fixtures.py b/tests/fixtures.py
index 00140f50..0330c8ed 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -101,6 +101,7 @@ def make_app_client(
extra_databases=None,
inspect_data=None,
static_mounts=None,
+ template_dir=None,
):
with tempfile.TemporaryDirectory() as tmpdir:
filepath = os.path.join(tmpdir, filename)
@@ -143,6 +144,7 @@ def make_app_client(
config=config,
inspect_data=inspect_data,
static_mounts=static_mounts,
+ template_dir=template_dir,
)
ds.sqlite_functions.append(("sleep", 1, lambda n: time.sleep(float(n))))
client = TestClient(ds.app())
diff --git a/tests/test_html.py b/tests/test_html.py
index 32fa2fe3..f76f98b9 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -964,3 +964,16 @@ def test_metadata_json_html(app_client):
assert response.status == 200
pre = Soup(response.body, "html.parser").find("pre")
assert METADATA == json.loads(pre.text)
+
+
+def test_custom_table_include():
+ for client in make_app_client(
+ template_dir=str(pathlib.Path(__file__).parent / "test_templates")
+ ):
+ response = client.get("/fixtures/complex_foreign_keys")
+ assert response.status == 200
+ assert (
+ ''
+ '1 - 2 -
hello 1'
+ "
"
+ ) == str(Soup(response.text, "html.parser").select_one("div.custom-table-row"))
diff --git a/tests/test_templates/_table.html b/tests/test_templates/_table.html
new file mode 100644
index 00000000..14f635a8
--- /dev/null
+++ b/tests/test_templates/_table.html
@@ -0,0 +1,3 @@
+{% for row in display_rows %}
+ {{ row["f1"] }} - {{ row["f2"] }} - {{ row.display("f3") }}
+{% endfor %}