mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Compare commits
5 commits
main
...
better-tem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1add905532 | ||
|
|
94d856c9a1 | ||
|
|
7d6b0d6762 | ||
|
|
43a5567be8 | ||
|
|
2b847240bb |
9 changed files with 119 additions and 82 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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, {})
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
<table>
|
If you want to output the rendered HTML version of a column, including any
|
||||||
<thead>
|
links to foreign keys, you can use ``{{ row.display("column_name") }}``.
|
||||||
<tr>
|
|
||||||
{% for column in display_columns %}
|
Here is an example of a custom ``_table.html`` template::
|
||||||
<th scope="col">{{ column }}</th>
|
|
||||||
{% endfor %}
|
{% for row in display_rows %}
|
||||||
</tr>
|
<div>
|
||||||
</thead>
|
<h2>{{ row["title"] }}</h2>
|
||||||
<tbody>
|
<p>{{ row["description"] }}<lp>
|
||||||
{% for row in display_rows %}
|
<p>Category: {{ row.display("category_id") }}</p>
|
||||||
<tr>
|
</div>
|
||||||
{% for cell in row %}
|
{% endfor %}
|
||||||
<td>
|
|
||||||
{% if cell.column == 'description' %}
|
|
||||||
{{ cell.value|safe }}
|
|
||||||
{% else %}
|
|
||||||
{{ cell.value }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
|
|
|
||||||
3
tests/test_templates/_table.html
Normal file
3
tests/test_templates/_table.html
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% for row in display_rows %}
|
||||||
|
<div class="custom-table-row">{{ row["f1"] }} - {{ row["f2"] }} - {{ row.display("f3") }}</div>
|
||||||
|
{% endfor %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue