Skip to content

Commit

Permalink
Datasette.render_template() method, closes #577
Browse files Browse the repository at this point in the history
Pull request #664.
  • Loading branch information
simonw committed Feb 4, 2020
1 parent 286ed28 commit 70b915f
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 83 deletions.
97 changes: 96 additions & 1 deletion datasette/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import asyncio
import collections
import hashlib
import itertools
import json
import os
import re
import sys
Expand All @@ -12,7 +14,8 @@

import click
from markupsafe import Markup
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader, escape
from jinja2.environment import Template
import uvicorn

from .views.base import DatasetteError, ureg, AsgiRouter
Expand All @@ -27,6 +30,7 @@
QueryInterrupted,
escape_css_string,
escape_sqlite,
format_bytes,
get_plugins,
module_from_path,
sqlite3,
Expand All @@ -35,6 +39,7 @@
from .utils.asgi import (
AsgiLifespan,
NotFound,
Response,
asgi_static,
asgi_send,
asgi_send_html,
Expand Down Expand Up @@ -526,6 +531,96 @@ def register_renderers(self):
for renderer in hook_renderers:
self.renderers[renderer["extension"]] = renderer["callback"]

async def render_template(
self, templates, context=None, request=None, view_name=None
):
context = context or {}
if isinstance(templates, Template):
template = templates
select_templates = []
else:
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,
"format_bytes": format_bytes,
"extra_css_urls": self._asset_urls("extra_css_urls", template, context),
"extra_js_urls": self._asset_urls("extra_js_urls", template, context),
},
**extra_template_vars,
}
return await template.render_async(template_context)

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,
)
),
(self.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 app(self):
"Returns an ASGI app function that serves the whole of Datasette"
default_templates = str(app_root / "datasette" / "templates")
Expand Down
93 changes: 11 additions & 82 deletions datasette/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@
import jinja2
import pint

from html import escape

from datasette import __version__
from datasette.plugins import pm
from datasette.utils import (
QueryInterrupted,
InvalidSql,
LimitedWriter,
format_bytes,
is_url,
path_with_added_args,
path_with_removed_args,
Expand Down Expand Up @@ -65,34 +62,6 @@ async def head(self, *args, **kwargs):
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:
Expand All @@ -105,62 +74,22 @@ def database_color(self, database):

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,
},
**extra_template_vars,
}
if request.args.get("_context") and self.ds.config("template_debug"):
**context,
**{
"database_url": self.database_url,
"database_color": self.database_color,
},
}
if request and request.args.get("_context") and self.ds.config("template_debug"):
return Response.html(
"<pre>{}</pre>".format(
escape(json.dumps(template_context, default=repr, indent=4))
jinja2.escape(json.dumps(template_context, default=repr, indent=4))
)
)
return Response.html(await template.render_async(template_context))
return Response.html(await self.ds.render_template(
template, template_context, request=request
))


class DataView(BaseView):
Expand Down

1 comment on commit 70b915f

@simonw
Copy link
Owner Author

@simonw simonw commented on 70b915f Apr 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out this commit caused two bugs - #689 and #716

Please sign in to comment.