From 3425d4924e35b10f915f8f9a911307026c5d12f7 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 3 Feb 2020 22:21:12 -0800 Subject: [PATCH] render_template refactor, refs #577 --- datasette/app.py | 90 ++++++++++++++++++++++++++++++++++++++ datasette/views/base.py | 95 +++++------------------------------------ 2 files changed, 100 insertions(+), 85 deletions(-) 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):