Compare commits

...

5 commits

Author SHA1 Message Date
Simon Willison
1add905532 Updated custom template docs, refs #521 2019-07-02 20:13:34 -07:00
Simon Willison
94d856c9a1 Unit test for _table custom template, refs #521 2019-07-02 20:06:22 -07:00
Simon Willison
7d6b0d6762 Rename _rows_and_columns.html to _table.html, refs #521 2019-07-02 17:51:54 -07:00
Simon Willison
43a5567be8 Default to raw value, use Row.display(key) for display, refs #521 2019-06-25 05:21:10 -07:00
Simon Willison
2b847240bb New experimental Row() for templates, refs #521 2019-06-25 05:02:42 -07:00
9 changed files with 119 additions and 82 deletions

View file

@ -24,7 +24,7 @@
<p>This data as {% for name, url in renderers.items() %}<a href="{{ url }}">{{ name }}</a>{{ ", " if not loop.last }}{% endfor %}</p> <p>This data as {% for name, url in renderers.items() %}<a href="{{ url }}">{{ name }}</a>{{ ", " if not loop.last }}{% endfor %}</p>
{% include custom_rows_and_columns_templates %} {% include custom_table_templates %}
{% if foreign_key_tables %} {% if foreign_key_tables %}
<h2>Links from other tables</h2> <h2>Links from other tables</h2>

View file

@ -145,7 +145,7 @@
</div> </div>
{% endif %} {% endif %}
{% include custom_rows_and_columns_templates %} {% include custom_table_templates %}
{% if next_url %} {% if next_url %}
<p><a href="{{ next_url }}">Next page</a></p> <p><a href="{{ next_url }}">Next page</a></p>

View file

@ -33,6 +33,35 @@ LINK_WITH_LABEL = (
LINK_WITH_VALUE = '<a href="/{database}/{table}/{link_id}">{id}</a>' LINK_WITH_VALUE = '<a href="/{database}/{table}/{link_id}">{id}</a>'
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): class RowTableShared(DataView):
async def sortable_columns_for_table(self, database, table, use_rowid): async def sortable_columns_for_table(self, database, table, use_rowid):
db = self.ds.databases[database] 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 # Unless we are a view, the first column is a link - either to the rowid
# or to the simple or compound primary key # or to the simple or compound primary key
if link_column: if link_column:
is_special_link_column = len(pks) != 1
pk_path = path_from_row_pks(row, pks, not pks, False)
cells.append( cells.append(
{ {
"column": pks[0] if len(pks) == 1 else "Link", "column": pks[0] if len(pks) == 1 else "Link",
"is_special_link_column": is_special_link_column,
"raw": pk_path,
"value": jinja2.Markup( "value": jinja2.Markup(
'<a href="/{database}/{table}/{flat_pks_quoted}">{flat_pks}</a>'.format( '<a href="/{database}/{table}/{flat_pks_quoted}">{flat_pks}</a>'.format(
database=database, database=database,
table=urllib.parse.quote_plus(table), table=urllib.parse.quote_plus(table),
flat_pks=str( flat_pks=str(jinja2.escape(pk_path)),
jinja2.escape(
path_from_row_pks(row, pks, not pks, False)
)
),
flat_pks_quoted=path_from_row_pks(row, pks, not pks), 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: if truncate_cells and len(display_value) > truncate_cells:
display_value = display_value[:truncate_cells] + u"\u2026" display_value = display_value[:truncate_cells] + u"\u2026"
cells.append({"column": column, "value": display_value}) cells.append({"column": column, "value": display_value, "raw": value})
cell_rows.append(cells) cell_rows.append(Row(cells))
if link_column: if link_column:
# Add the link column header. # Add the link column header.
@ -715,14 +744,14 @@ class TableView(RowTableShared):
"sort": sort, "sort": sort,
"sort_desc": sort_desc, "sort_desc": sort_desc,
"disable_sort": is_view, "disable_sort": is_view,
"custom_rows_and_columns_templates": [ "custom_table_templates": [
"_rows_and_columns-{}-{}.html".format( "_table-{}-{}.html".format(
to_css_class(database), to_css_class(table) 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) to_css_class(database), to_css_class(table)
), ),
"_rows_and_columns.html", "_table.html",
], ],
"metadata": metadata, "metadata": metadata,
"view_definition": await db.get_view_definition(table), "view_definition": await db.get_view_definition(table),
@ -799,14 +828,14 @@ class RowView(RowTableShared):
), ),
"display_columns": display_columns, "display_columns": display_columns,
"display_rows": display_rows, "display_rows": display_rows,
"custom_rows_and_columns_templates": [ "custom_table_templates": [
"_rows_and_columns-{}-{}.html".format( "_table-{}-{}.html".format(
to_css_class(database), to_css_class(table) 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) to_css_class(database), to_css_class(table)
), ),
"_rows_and_columns.html", "_table.html",
], ],
"metadata": (self.ds.metadata("databases") or {}) "metadata": (self.ds.metadata("databases") or {})
.get(database, {}) .get(database, {})

View file

@ -144,15 +144,15 @@ The lookup rules Datasette uses are as follows::
row-mydatabase-mytable.html row-mydatabase-mytable.html
row.html row.html
Rows and columns include on table page: Table of rows and columns include on table page:
_rows_and_columns-table-mydatabase-mytable.html _table-table-mydatabase-mytable.html
_rows_and_columns-mydatabase-mytable.html _table-mydatabase-mytable.html
_rows_and_columns.html _table.html
Rows and columns include on row page: Table of rows and columns include on row page:
_rows_and_columns-row-mydatabase-mytable.html _table-row-mydatabase-mytable.html
_rows_and_columns-mydatabase-mytable.html _table-mydatabase-mytable.html
_rows_and_columns.html _table.html
If a table name has spaces or other unexpected characters in it, the template If a table name has spaces or other unexpected characters in it, the template
filename will follow the same rules as our custom ``<body>`` CSS classes - for filename will follow the same rules as our custom ``<body>`` 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 Note the ``default:row.html`` template name, which ensures Jinja will inherit
from the default template. from the default template.
The ``_rows_and_columns.html`` template is included on both the row and the table The ``_table.html`` template is included by both the row and the table pages,
page, and displays the content of the row. The default ``_rows_and_columns.html`` template and a list of rows. The default ``_table.html`` template renders them as an
`can be seen here <https://github.com/simonw/datasette/blob/master/datasette/templates/_rows_and_columns.html>`_. HTML template and `can be seen here <https://github.com/simonw/datasette/blob/master/datasette/templates/_table.html>`_.
You can provide a custom template that applies to all of your databases and 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 tables, or you can provide custom templates for specific tables using the
template naming scheme described above. template naming scheme described above.
Say for example you want to output a certain column as unescaped HTML. You could If you want to present your data in a format other than an HTML table, you
provide a custom ``_rows_and_columns.html`` template like this:: 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.
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::
<table>
<thead>
<tr>
{% for column in display_columns %}
<th scope="col">{{ column }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in display_rows %} {% for row in display_rows %}
<tr> <div>
{% for cell in row %} <h2>{{ row["title"] }}</h2>
<td> <p>{{ row["description"] }}<lp>
{% if cell.column == 'description' %} <p>Category: {{ row.display("category_id") }}</p>
{{ cell.value|safe }} </div>
{% else %}
{{ cell.value }}
{% endif %}
</td>
{% endfor %} {% endfor %}
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -101,6 +101,7 @@ def make_app_client(
extra_databases=None, extra_databases=None,
inspect_data=None, inspect_data=None,
static_mounts=None, static_mounts=None,
template_dir=None,
): ):
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
filepath = os.path.join(tmpdir, filename) filepath = os.path.join(tmpdir, filename)
@ -143,6 +144,7 @@ def make_app_client(
config=config, config=config,
inspect_data=inspect_data, inspect_data=inspect_data,
static_mounts=static_mounts, static_mounts=static_mounts,
template_dir=template_dir,
) )
ds.sqlite_functions.append(("sleep", 1, lambda n: time.sleep(float(n)))) ds.sqlite_functions.append(("sleep", 1, lambda n: time.sleep(float(n))))
client = TestClient(ds.app()) client = TestClient(ds.app())

View file

@ -964,3 +964,16 @@ def test_metadata_json_html(app_client):
assert response.status == 200 assert response.status == 200
pre = Soup(response.body, "html.parser").find("pre") pre = Soup(response.body, "html.parser").find("pre")
assert METADATA == json.loads(pre.text) 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 (
'<div class="custom-table-row">'
'1 - 2 - <a href="/fixtures/simple_primary_key/1">hello</a> <em>1</em>'
"</div>"
) == str(Soup(response.text, "html.parser").select_one("div.custom-table-row"))

View file

@ -0,0 +1,3 @@
{% for row in display_rows %}
<div class="custom-table-row">{{ row["f1"] }} - {{ row["f2"] }} - {{ row.display("f3") }}</div>
{% endfor %}