diff --git a/datasette/app.py b/datasette/app.py index cfce8e0b..cee5ae21 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -46,6 +46,7 @@ from .database import Database, QueryInterrupted from .utils import ( PrefixedUrlString, + PrependingLoader, StartupError, async_call_with_supported_arguments, await_me_maybe, @@ -309,7 +310,9 @@ class Datasette: ] ) self.jinja_env = Environment( - loader=template_loader, autoescape=True, enable_async=True + loader=PrependingLoader(template_loader, "_macros.html"), + autoescape=True, + enable_async=True, ) self.jinja_env.filters["escape_css_string"] = escape_css_string self.jinja_env.filters["quote_plus"] = lambda u: urllib.parse.quote_plus(u) @@ -759,6 +762,14 @@ class Datasette: renderer.get("can_render") or (lambda: True), ) + def _include_templates(self, name, **kwargs): + include_templates = [] + for templates in getattr(pm.hook, name)(**kwargs): + if isinstance(templates, str): + templates = [templates] + include_templates.extend(templates) + return include_templates + async def render_template( self, templates, context=None, request=None, view_name=None ): diff --git a/datasette/hookspecs.py b/datasette/hookspecs.py index 240bf80f..13a10680 100644 --- a/datasette/hookspecs.py +++ b/datasette/hookspecs.py @@ -112,53 +112,3 @@ def table_actions(datasette, actor, database, table): @hookspec def database_actions(datasette, actor, database): """Links for the database actions menu""" - - -@hookspec -def include_table_top(datasette, database, actor, table): - """Templates to include at the top of the table page""" - - -@hookspec -def include_table_bottom(datasette, database, actor, table): - """Templates to include at the bottom of the table page""" - - -@hookspec -def include_row_top(datasette, database, actor, table): - """Templates to include at the top of the row page""" - - -@hookspec -def include_row_bottom(datasette, database, actor, table): - """Templates to include at the bottom of the row page""" - - -@hookspec -def include_database_top(datasette, database, actor): - """Templates to include at the top of the database page""" - - -@hookspec -def include_database_bottom(datasette, database, actor): - """Templates to include at the bottom of the database page""" - - -@hookspec -def include_query_top(datasette, database, actor): - """Templates to include at the top of the query page""" - - -@hookspec -def include_query_bottom(datasette, database, actor): - """Templates to include at the bottom of the query page""" - - -@hookspec -def include_index_top(datasette, actor): - """Templates to include at the top of the index page""" - - -@hookspec -def include_index_bottom(datasette, actor): - """Templates to include at the bottom of the index page""" diff --git a/datasette/templates/table.html b/datasette/templates/table.html index 077332dc..88cfef22 100644 --- a/datasette/templates/table.html +++ b/datasette/templates/table.html @@ -51,6 +51,8 @@ {% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %} +{{ includes("table-top-includes", table_top_includes) }} + {% if filtered_table_rows_count or human_description_en %}

{% if filtered_table_rows_count or filtered_table_rows_count == 0 %}{{ "{:,}".format(filtered_table_rows_count) }} row{% if filtered_table_rows_count == 1 %}{% else %}s{% endif %}{% endif %} {% if human_description_en %}{{ human_description_en }}{% endif %} @@ -201,6 +203,8 @@ {% endif %} +{{ includes("table-bottom-includes", table_bottom_includes) }} + {% if table_definition %}
{{ table_definition }}
{% endif %} diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index 47ca0551..70f25002 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -18,6 +18,7 @@ import shutil import urllib import numbers import yaml +from jinja2 import BaseLoader from .shutil_backport import copytree from .sqlite import sqlite3, sqlite_version, supports_table_xinfo from ..plugins import pm @@ -1068,3 +1069,26 @@ class PrefixedUrlString(str): class StartupError(Exception): pass + + +class PrependingLoader(BaseLoader): + # Based on http://codyaray.com/2015/05/auto-load-jinja2-macros + def __init__(self, delegate, prepend_template): + self.delegate = delegate + self.prepend_template = prepend_template + + def get_source(self, environment, template): + prepend_source, _, prepend_uptodate = self.delegate.get_source( + environment, self.prepend_template + ) + main_source, main_filename, main_uptodate = self.delegate.get_source( + environment, template + ) + uptodate = lambda: prepend_uptodate() and main_uptodate() + return prepend_source + main_source, main_filename, uptodate + + def list_templates(self): + return self.delegate.list_templates() + + def select_template(self, *args, **kwargs): + return self.delegate.select_template(*args, **kwargs) diff --git a/datasette/views/table.py b/datasette/views/table.py index cc8ef9f1..a921e9f7 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -880,6 +880,20 @@ class TableView(RowTableShared): "metadata": metadata, "view_definition": await db.get_view_definition(table), "table_definition": await db.get_table_definition(table), + "table_top_includes": self.ds._include_templates( + "include_table_top", + database=database, + table=table, + actor=request.actor, + datasette=self.ds, + ), + "table_bottom_includes": self.ds._include_templates( + "include_table_bottom", + database=database, + table=table, + actor=request.actor, + datasette=self.ds, + ), } return (