kopia lustrzana https://github.com/simonw/datasette
rodzic
b51f258d00
commit
8c642f04e0
|
@ -583,7 +583,9 @@ class Datasette:
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
self.jinja_env = Environment(loader=template_loader, autoescape=True)
|
self.jinja_env = Environment(
|
||||||
|
loader=template_loader, autoescape=True, enable_async=True
|
||||||
|
)
|
||||||
self.jinja_env.filters["escape_css_string"] = escape_css_string
|
self.jinja_env.filters["escape_css_string"] = escape_css_string
|
||||||
self.jinja_env.filters["quote_plus"] = lambda u: urllib.parse.quote_plus(u)
|
self.jinja_env.filters["quote_plus"] = lambda u: urllib.parse.quote_plus(u)
|
||||||
self.jinja_env.filters["escape_sqlite"] = escape_sqlite
|
self.jinja_env.filters["escape_sqlite"] = escape_sqlite
|
||||||
|
@ -730,5 +732,5 @@ class DatasetteRouter(AsgiRouter):
|
||||||
else:
|
else:
|
||||||
template = self.ds.jinja_env.select_template(templates)
|
template = self.ds.jinja_env.select_template(templates)
|
||||||
await asgi_send_html(
|
await asgi_send_html(
|
||||||
send, template.render(info), status=status, headers=headers
|
send, await template.render_async(info), status=status, headers=headers
|
||||||
)
|
)
|
||||||
|
|
|
@ -139,7 +139,7 @@ class BaseView(AsgiView):
|
||||||
extra_template_vars.update(extra_vars)
|
extra_template_vars.update(extra_vars)
|
||||||
|
|
||||||
return Response.html(
|
return Response.html(
|
||||||
template.render(
|
await template.render_async(
|
||||||
{
|
{
|
||||||
**context,
|
**context,
|
||||||
**{
|
**{
|
||||||
|
|
|
@ -629,7 +629,9 @@ Function that returns a dictionary
|
||||||
If you return a function it will be executed. If it returns a dictionary those values will will be merged into the template context.
|
If you return a function it will be executed. If it returns a dictionary those values will will be merged into the template context.
|
||||||
|
|
||||||
Function that returns an awaitable function that returns a dictionary
|
Function that returns an awaitable function that returns a dictionary
|
||||||
You can also return a function which returns an awaitable function which returns a dictionary. This means you can execute additional SQL queries using ``datasette.execute()``.
|
You can also return a function which returns an awaitable function which returns a dictionary.
|
||||||
|
|
||||||
|
Datasette runs Jinja2 in `async mode <https://jinja.palletsprojects.com/en/2.10.x/api/#async-support>`__, which means you can add awaitable functions to the template scope and they will be automatically awaited when they are rendered by the template.
|
||||||
|
|
||||||
Here's an example plugin that returns an authentication object from the ASGI scope:
|
Here's an example plugin that returns an authentication object from the ASGI scope:
|
||||||
|
|
||||||
|
@ -641,20 +643,19 @@ Here's an example plugin that returns an authentication object from the ASGI sco
|
||||||
"auth": request.scope.get("auth")
|
"auth": request.scope.get("auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
And here's an example which returns the current version of SQLite:
|
And here's an example which adds a ``sql_first(sql_query)`` function which executes a SQL statement and returns the first column of the first row of results:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def extra_template_vars(datasette):
|
def extra_template_vars(datasette, database):
|
||||||
async def inner():
|
async def sql_first(sql, dbname=None):
|
||||||
first_db = list(datasette.databases.keys())[0]
|
dbname = dbname or database or next(iter(datasette.databases.keys()))
|
||||||
return {
|
return (await datasette.execute(dbname, sql)).rows[0][0]
|
||||||
"sqlite_version": (
|
|
||||||
await datasette.execute(first_db, "select sqlite_version()")
|
You can then use the new function in a template like so::
|
||||||
).rows[0][0]
|
|
||||||
}
|
SQLite version: {{ sql_first("select sqlite_version()") }}
|
||||||
return inner
|
|
||||||
|
|
||||||
.. _plugin_register_output_renderer:
|
.. _plugin_register_output_renderer:
|
||||||
|
|
||||||
|
|
|
@ -446,13 +446,19 @@ def render_cell(value, database):
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def extra_template_vars(template, database, table, view_name, request, datasette):
|
def extra_template_vars(template, database, table, view_name, request, datasette):
|
||||||
|
async def query_database(sql):
|
||||||
|
first_db = list(datasette.databases.keys())[0]
|
||||||
|
return (
|
||||||
|
await datasette.execute(first_db, sql)
|
||||||
|
).rows[0][0]
|
||||||
async def inner():
|
async def inner():
|
||||||
return {
|
return {
|
||||||
"extra_template_vars_from_awaitable": json.dumps({
|
"extra_template_vars_from_awaitable": json.dumps({
|
||||||
"template": template,
|
"template": template,
|
||||||
"scope_path": request.scope["path"],
|
"scope_path": request.scope["path"],
|
||||||
"awaitable": True,
|
"awaitable": True,
|
||||||
}, default=lambda b: b.decode("utf8"))
|
}, default=lambda b: b.decode("utf8")),
|
||||||
|
"query_database": query_database,
|
||||||
}
|
}
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from bs4 import BeautifulSoup as Soup
|
from bs4 import BeautifulSoup as Soup
|
||||||
from .fixtures import app_client, make_app_client, TEMP_PLUGIN_SECRET_FILE # noqa
|
from .fixtures import app_client, make_app_client, TEMP_PLUGIN_SECRET_FILE # noqa
|
||||||
|
from datasette.utils import sqlite3
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -214,3 +215,20 @@ def test_plugins_extra_template_vars(restore_working_directory):
|
||||||
"awaitable": True,
|
"awaitable": True,
|
||||||
"scope_path": "/-/metadata",
|
"scope_path": "/-/metadata",
|
||||||
} == extra_template_vars_from_awaitable
|
} == extra_template_vars_from_awaitable
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugins_async_template_function(restore_working_directory):
|
||||||
|
for client in make_app_client(
|
||||||
|
template_dir=str(pathlib.Path(__file__).parent / "test_templates")
|
||||||
|
):
|
||||||
|
response = client.get("/-/metadata")
|
||||||
|
assert response.status == 200
|
||||||
|
extra_from_awaitable_function = (
|
||||||
|
Soup(response.body, "html.parser")
|
||||||
|
.select("pre.extra_from_awaitable_function")[0]
|
||||||
|
.text
|
||||||
|
)
|
||||||
|
expected = (
|
||||||
|
sqlite3.connect(":memory:").execute("select sqlite_version()").fetchone()[0]
|
||||||
|
)
|
||||||
|
assert expected == extra_from_awaitable_function
|
||||||
|
|
|
@ -5,4 +5,5 @@
|
||||||
Test data for extra_template_vars:
|
Test data for extra_template_vars:
|
||||||
<pre class="extra_template_vars">{{ extra_template_vars|safe }}</pre>
|
<pre class="extra_template_vars">{{ extra_template_vars|safe }}</pre>
|
||||||
<pre class="extra_template_vars_from_awaitable">{{ extra_template_vars_from_awaitable|safe }}</pre>
|
<pre class="extra_template_vars_from_awaitable">{{ extra_template_vars_from_awaitable|safe }}</pre>
|
||||||
|
<pre class="extra_from_awaitable_function">{{ query_database("select sqlite_version();") }}</pre>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Ładowanie…
Reference in New Issue