diff --git a/datasette/app.py b/datasette/app.py index f6b3e514..2ab9b1b3 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -27,6 +27,7 @@ from .utils import ( QueryInterrupted, escape_css_string, escape_sqlite, + format_bytes, get_plugins, module_from_path, sqlite3, @@ -526,6 +527,95 @@ class Datasette: for renderer in hook_renderers: self.renderers[renderer["extension"]] = renderer["callback"] + def _asset_urls(self, key, template, context): + # Flatten list-of-lists from plugins: + seen_urls = set() + for url_or_dict in itertools.chain( + itertools.chain.from_iterable( + getattr(pm.hook, key)( + template=template.name, + database=context.get("database"), + table=context.get("table"), + datasette=self.ds, + ) + ), + (self.ds.metadata(key) or []), + ): + if isinstance(url_or_dict, dict): + url = url_or_dict["url"] + sri = url_or_dict.get("sri") + else: + url = url_or_dict + sri = None + if url in seen_urls: + continue + seen_urls.add(url) + if sri: + yield {"url": url, "sri": sri} + else: + yield {"url": url} + + async def render_template( + self, templates, context=None, request=None, view_name=None + ): + if isinstance(templates, str): + templates = [templates] + template = self.jinja_env.select_template(templates) + select_templates = [ + "{}{}".format("*" if template_name == template.name else "", template_name) + for template_name in templates + ] + body_scripts = [] + # pylint: disable=no-member + for script in pm.hook.extra_body_script( + template=template.name, + database=context.get("database"), + table=context.get("table"), + view_name=view_name, + datasette=self, + ): + body_scripts.append(Markup(script)) + + extra_template_vars = {} + # pylint: disable=no-member + for extra_vars in pm.hook.extra_template_vars( + template=template.name, + database=context.get("database"), + table=context.get("table"), + view_name=view_name, + request=request, + datasette=self, + ): + if callable(extra_vars): + extra_vars = extra_vars() + if asyncio.iscoroutine(extra_vars): + extra_vars = await extra_vars + assert isinstance(extra_vars, dict), "extra_vars is of type {}".format( + type(extra_vars) + ) + extra_template_vars.update(extra_vars) + + template_context = { + **context, + **{ + "app_css_hash": self.app_css_hash(), + "select_templates": select_templates, + "zip": zip, + "body_scripts": body_scripts, + "extra_css_urls": self._asset_urls("extra_css_urls", template, context), + "extra_js_urls": self._asset_urls("extra_js_urls", template, context), + "format_bytes": format_bytes, + }, + **extra_template_vars, + } + if request.args.get("_context") and self.config("template_debug"): + return Response.html( + "
{}".format(
+ escape(json.dumps(template_context, default=repr, indent=4))
+ )
+ )
+ return Response.html(await template.render_async(template_context))
+
def app(self):
"Returns an ASGI app function that serves the whole of Datasette"
default_templates = str(app_root / "datasette" / "templates")
diff --git a/datasette/views/base.py b/datasette/views/base.py
index 61561a32..a048fe3d 100644
--- a/datasette/views/base.py
+++ b/datasette/views/base.py
@@ -17,7 +17,6 @@ from datasette.utils import (
QueryInterrupted,
InvalidSql,
LimitedWriter,
- format_bytes,
is_url,
path_with_added_args,
path_with_removed_args,
@@ -65,34 +64,6 @@ class BaseView(AsgiView):
response.body = b""
return response
- def _asset_urls(self, key, template, context):
- # Flatten list-of-lists from plugins:
- seen_urls = set()
- for url_or_dict in itertools.chain(
- itertools.chain.from_iterable(
- getattr(pm.hook, key)(
- template=template.name,
- database=context.get("database"),
- table=context.get("table"),
- datasette=self.ds,
- )
- ),
- (self.ds.metadata(key) or []),
- ):
- if isinstance(url_or_dict, dict):
- url = url_or_dict["url"]
- sri = url_or_dict.get("sri")
- else:
- url = url_or_dict
- sri = None
- if url in seen_urls:
- continue
- seen_urls.add(url)
- if sri:
- yield {"url": url, "sri": sri}
- else:
- yield {"url": url}
-
def database_url(self, database):
db = self.ds.databases[database]
if self.ds.config("hash_urls") and db.hash:
@@ -104,63 +75,17 @@ class BaseView(AsgiView):
return "ff0000"
async def render(self, templates, request, context):
- template = self.ds.jinja_env.select_template(templates)
- select_templates = [
- "{}{}".format("*" if template_name == template.name else "", template_name)
- for template_name in templates
- ]
- body_scripts = []
- # pylint: disable=no-member
- for script in pm.hook.extra_body_script(
- template=template.name,
- database=context.get("database"),
- table=context.get("table"),
- view_name=self.name,
- datasette=self.ds,
- ):
- body_scripts.append(jinja2.Markup(script))
-
- extra_template_vars = {}
- # pylint: disable=no-member
- for extra_vars in pm.hook.extra_template_vars(
- template=template.name,
- database=context.get("database"),
- table=context.get("table"),
- view_name=self.name,
- request=request,
- datasette=self.ds,
- ):
- if callable(extra_vars):
- extra_vars = extra_vars()
- if asyncio.iscoroutine(extra_vars):
- extra_vars = await extra_vars
- assert isinstance(extra_vars, dict), "extra_vars is of type {}".format(
- type(extra_vars)
- )
- extra_template_vars.update(extra_vars)
-
- template_context = {
- **context,
- **{
- "app_css_hash": self.ds.app_css_hash(),
- "select_templates": select_templates,
- "zip": zip,
- "body_scripts": body_scripts,
- "extra_css_urls": self._asset_urls("extra_css_urls", template, context),
- "extra_js_urls": self._asset_urls("extra_js_urls", template, context),
- "format_bytes": format_bytes,
- "database_url": self.database_url,
- "database_color": self.database_color,
+ return await self.ds.render_template(
+ templates,
+ {
+ **context,
+ **{
+ "database_url": self.database_url,
+ "database_color": self.database_color,
+ },
},
- **extra_template_vars,
- }
- if request.args.get("_context") and self.ds.config("template_debug"):
- return Response.html(
- "{}".format(
- escape(json.dumps(template_context, default=repr, indent=4))
- )
- )
- return Response.html(await template.render_async(template_context))
+ request=request,
+ )
class DataView(BaseView):