mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
parent
fcf43589eb
commit
81dea4b07a
8 changed files with 73 additions and 12 deletions
|
|
@ -21,7 +21,7 @@ from pathlib import Path
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
from itsdangerous import URLSafeSerializer
|
from itsdangerous import URLSafeSerializer
|
||||||
import jinja2
|
import jinja2
|
||||||
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader, escape
|
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
|
||||||
from jinja2.environment import Template
|
from jinja2.environment import Template
|
||||||
from jinja2.exceptions import TemplateNotFound
|
from jinja2.exceptions import TemplateNotFound
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
@ -713,12 +713,41 @@ class Datasette:
|
||||||
self, templates, context=None, request=None, view_name=None
|
self, templates, context=None, request=None, view_name=None
|
||||||
):
|
):
|
||||||
context = context or {}
|
context = context or {}
|
||||||
|
templates_considered = []
|
||||||
if isinstance(templates, Template):
|
if isinstance(templates, Template):
|
||||||
template = templates
|
template = templates
|
||||||
else:
|
else:
|
||||||
if isinstance(templates, str):
|
if isinstance(templates, str):
|
||||||
templates = [templates]
|
templates = [templates]
|
||||||
template = self.jinja_env.select_template(templates)
|
|
||||||
|
# Give plugins first chance at loading the template
|
||||||
|
break_outer = False
|
||||||
|
plugin_template_source = None
|
||||||
|
plugin_template_name = None
|
||||||
|
template_name = None
|
||||||
|
for template_name in templates:
|
||||||
|
if break_outer:
|
||||||
|
break
|
||||||
|
plugin_template_source = pm.hook.load_template(
|
||||||
|
template=template_name,
|
||||||
|
request=request,
|
||||||
|
datasette=self,
|
||||||
|
)
|
||||||
|
plugin_template_source = await await_me_maybe(plugin_template_source)
|
||||||
|
if plugin_template_source:
|
||||||
|
break_outer = True
|
||||||
|
plugin_template_name = template_name
|
||||||
|
break
|
||||||
|
if plugin_template_source is not None:
|
||||||
|
template = self.jinja_env.from_string(plugin_template_source)
|
||||||
|
else:
|
||||||
|
template = self.jinja_env.select_template(templates)
|
||||||
|
for template_name in templates:
|
||||||
|
from_plugin = template_name == plugin_template_name
|
||||||
|
used = from_plugin or template_name == template.name
|
||||||
|
templates_considered.append(
|
||||||
|
{"name": template_name, "used": used, "from_plugin": from_plugin}
|
||||||
|
)
|
||||||
body_scripts = []
|
body_scripts = []
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
for extra_script in pm.hook.extra_body_script(
|
for extra_script in pm.hook.extra_body_script(
|
||||||
|
|
@ -783,6 +812,7 @@ class Datasette:
|
||||||
),
|
),
|
||||||
"base_url": self.config("base_url"),
|
"base_url": self.config("base_url"),
|
||||||
"csrftoken": request.scope["csrftoken"] if request else lambda: "",
|
"csrftoken": request.scope["csrftoken"] if request else lambda: "",
|
||||||
|
"templates_considered": templates_considered,
|
||||||
},
|
},
|
||||||
**extra_template_vars,
|
**extra_template_vars,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,11 @@ def extra_template_vars(
|
||||||
"Extra template variables to be made available to the template - can return dict or callable or awaitable"
|
"Extra template variables to be made available to the template - can return dict or callable or awaitable"
|
||||||
|
|
||||||
|
|
||||||
|
@hookspec(firstresult=True)
|
||||||
|
def load_template(template, request, datasette):
|
||||||
|
"Load the specified template, returning the template code as a string"
|
||||||
|
|
||||||
|
|
||||||
@hookspec
|
@hookspec
|
||||||
def publish_subcommand(publish):
|
def publish_subcommand(publish):
|
||||||
"Subcommands for 'datasette publish'"
|
"Subcommands for 'datasette publish'"
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@ document.body.addEventListener('click', (ev) => {
|
||||||
<script>{{ body_script }}</script>
|
<script>{{ body_script }}</script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if select_templates %}<!-- Templates considered: {{ select_templates|join(", ") }} -->{% endif %}
|
{% if templates_considered %}
|
||||||
|
<!-- Templates considered:
|
||||||
|
{% for template in templates_considered %}- {{ template.name }}{% if template.used %} (used{% if template.from_plugin %}, from plugin{% endif %}){% endif %}
|
||||||
|
{% endfor %}-->
|
||||||
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import urllib
|
||||||
import pint
|
import pint
|
||||||
|
|
||||||
from datasette import __version__
|
from datasette import __version__
|
||||||
from datasette.plugins import pm
|
|
||||||
from datasette.database import QueryInterrupted
|
from datasette.database import QueryInterrupted
|
||||||
from datasette.utils import (
|
from datasette.utils import (
|
||||||
await_me_maybe,
|
await_me_maybe,
|
||||||
|
|
@ -119,22 +118,15 @@ class BaseView:
|
||||||
|
|
||||||
async def render(self, templates, request, context=None):
|
async def render(self, templates, request, context=None):
|
||||||
context = context or {}
|
context = context or {}
|
||||||
template = self.ds.jinja_env.select_template(templates)
|
|
||||||
template_context = {
|
template_context = {
|
||||||
**context,
|
**context,
|
||||||
**{
|
**{
|
||||||
"database_color": self.database_color,
|
"database_color": self.database_color,
|
||||||
"select_templates": [
|
|
||||||
"{}{}".format(
|
|
||||||
"*" if template_name == template.name else "", template_name
|
|
||||||
)
|
|
||||||
for template_name in templates
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return Response.html(
|
return Response.html(
|
||||||
await self.ds.render_template(
|
await self.ds.render_template(
|
||||||
template, template_context, request=request, view_name=self.name
|
templates, template_context, request=request, view_name=self.name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,24 @@ You can also return an awaitable function that returns a string.
|
||||||
|
|
||||||
Example: `datasette-cluster-map <https://github.com/simonw/datasette-cluster-map>`_
|
Example: `datasette-cluster-map <https://github.com/simonw/datasette-cluster-map>`_
|
||||||
|
|
||||||
|
.. _plugin_hook_load_template:
|
||||||
|
|
||||||
|
load_template(template, request, datasette)
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
``template`` - string
|
||||||
|
The template that is being rendered, e.g. ``database.html``
|
||||||
|
|
||||||
|
``request`` - object or None
|
||||||
|
The current HTTP :ref:`internals_request`. This can be ``None`` if the request object is not available.
|
||||||
|
|
||||||
|
``datasette`` - :ref:`internals_datasette`
|
||||||
|
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``
|
||||||
|
|
||||||
|
Load the source code for a template from a custom location. Hooks should return a string, or ``None`` if the template is not found.
|
||||||
|
|
||||||
|
Datasette will fall back to serving templates from files on disk if the requested template cannot be loaded by any plugins.
|
||||||
|
|
||||||
.. _plugin_hook_publish_subcommand:
|
.. _plugin_hook_publish_subcommand:
|
||||||
|
|
||||||
publish_subcommand(publish)
|
publish_subcommand(publish)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ EXPECTED_PLUGINS = [
|
||||||
"extra_js_urls",
|
"extra_js_urls",
|
||||||
"extra_template_vars",
|
"extra_template_vars",
|
||||||
"forbidden",
|
"forbidden",
|
||||||
|
"load_template",
|
||||||
"menu_links",
|
"menu_links",
|
||||||
"permission_allowed",
|
"permission_allowed",
|
||||||
"prepare_connection",
|
"prepare_connection",
|
||||||
|
|
|
||||||
|
|
@ -308,3 +308,9 @@ def table_actions(datasette, database, table, actor):
|
||||||
},
|
},
|
||||||
{"href": datasette.urls.instance(), "label": "Table: {}".format(table)},
|
{"href": datasette.urls.instance(), "label": "Table: {}".format(table)},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def load_template(template, request):
|
||||||
|
if template == "show_json.html" and request.args.get("_special"):
|
||||||
|
return "<h1>Special show_json: {{ filename }}</h1>"
|
||||||
|
|
|
||||||
|
|
@ -801,3 +801,8 @@ def test_hook_table_actions(app_client):
|
||||||
{"label": "Database: fixtures", "href": "/"},
|
{"label": "Database: fixtures", "href": "/"},
|
||||||
{"label": "Table: facetable", "href": "/"},
|
{"label": "Table: facetable", "href": "/"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_hook_load_template(app_client):
|
||||||
|
response = app_client.get("/-/databases?_special=1")
|
||||||
|
assert response.text == "<h1>Special show_json: databases.json</h1>"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue