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 %} - - {% endfor %} - - - - {% for row in display_rows %} - - {% for cell in row %} - - {% endfor %} - - {% endfor %} - -
{{ column }}
- {% if cell.column == 'description' %} - {{ cell.value|safe }} - {% else %} - {{ cell.value }} - {% endif %} -
+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 %}