Skip to content

Commit

Permalink
top_homepage() plugin hook, refs #1191
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Jan 30, 2024
1 parent 7a5adb5 commit 0c814ce
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 2 deletions.
25 changes: 25 additions & 0 deletions datasette/hookspecs.py
Expand Up @@ -158,3 +158,28 @@ def skip_csrf(datasette, scope):
@hookspec
def handle_exception(datasette, request, exception):
"""Handle an uncaught exception. Can return a Response or None."""


@hookspec
def top_homepage(datasette, request):
"""HTML to include at the top of the homepage"""


# @hookspec
# def top_database(datasette, request, database):
# """HTML to include at the top of the database page"""


# @hookspec
# def top_table(datasette, request, database, table):
# """HTML to include at the top of the table page"""


# @hookspec
# def top_row(datasette, request, database, table, row):
# """HTML to include at the top of the row page"""


# @hookspec
# def top_query(datasette, request, database, query):
# """HTML to include at the top of the query page"""
2 changes: 2 additions & 0 deletions datasette/templates/index.html
Expand Up @@ -7,6 +7,8 @@
{% block content %}
<h1>{{ metadata.title or "Datasette" }}{% if private %} 🔒{% endif %}</h1>

{{ top_homepage() }}

{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}

{% for database in databases %}
Expand Down
25 changes: 23 additions & 2 deletions datasette/views/index.py
@@ -1,10 +1,12 @@
import hashlib
import json

from datasette.utils import add_cors_headers, CustomJSONEncoder
from datasette.plugins import pm
from datasette.utils import add_cors_headers, await_me_maybe, CustomJSONEncoder
from datasette.utils.asgi import Response
from datasette.version import __version__

from markupsafe import Markup

from .base import BaseView


Expand Down Expand Up @@ -142,5 +144,24 @@ async def get(self, request):
"private": not await self.ds.permission_allowed(
None, "view-instance"
),
"top_homepage": include_block_function(
"top_homepage", self.ds, request
),
},
)


def include_block_function(name, datasette, request, **kwargs):
method = getattr(pm.hook, name, None)
if method is None:
raise Exception("No hook found for {}".format(name))

Check warning on line 157 in datasette/views/index.py

View check run for this annotation

Codecov / codecov/patch

datasette/views/index.py#L157

Added line #L157 was not covered by tests

async def inner():
html_bits = []
for hook in method(datasette=datasette, request=request, **kwargs):
html = await await_me_maybe(hook)
if html is not None:
html_bits.append(html)
return Markup("".join(html_bits))

return inner
115 changes: 115 additions & 0 deletions docs/plugin_hooks.rst
Expand Up @@ -1641,3 +1641,118 @@ This hook is responsible for returning a dictionary corresponding to Datasette :
return metadata
Example: `datasette-remote-metadata plugin <https://datasette.io/plugins/datasette-remote-metadata>`__


@hookimpl
def top_homepage(datasette, request):
"""HTML to include at the top of the homepage"""


@hookimpl
def top_database(datasette, request, database):
"""HTML to include at the top of the database page"""


@hookimpl
def top_table(datasette, request, database, table):
"""HTML to include at the top of the table page"""


@hookimpl
def top_row(datasette, request, database, table, row):
"""HTML to include at the top of the row page"""


@hookimpl
def top_query(datasette, request, database, query):
"""HTML to include at the top of the query page"""


.. _plugin_hook_top_homepage:

top_homepage(datasette, request)
--------------------------------

``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``.

``request`` - :ref:`internals_request`
The current HTTP request.

Returns HTML to be displayed at the top of the Datasette homepage.

.. _plugin_hook_top_database:

top_database(datasette, request, database)
------------------------------------------

``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``.

``request`` - :ref:`internals_request`
The current HTTP request.

``database`` - string
The name of the database.

Returns HTML to be displayed at the top of the database page.

.. _plugin_hook_top_table:

top_table(datasette, request, database, table)
---------------------------------------------

``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``.

``request`` - :ref:`internals_request`
The current HTTP request.

``database`` - string
The name of the database.

``table`` - string
The name of the table.

Returns HTML to be displayed at the top of the table page.

.. _plugin_hook_top_row:

top_row(datasette, request, database, table, row)
------------------------------------------------

``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``.

``request`` - :ref:`internals_request`
The current HTTP request.

``database`` - string
The name of the database.

``table`` - string
The name of the table.

``row`` - ``sqlite.Row``
The SQLite row object being displayed.

Returns HTML to be displayed at the top of the row page.

.. _plugin_hook_top_query:

top_query(datasette, request, database, query)
---------------------------------------------

``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``.

``request`` - :ref:`internals_request`
The current HTTP request.

``database`` - string
The name of the database.

``query`` - string
The name of the canned query.

Returns HTML to be displayed at the top of the canned query page.
19 changes: 19 additions & 0 deletions tests/test_plugins.py
Expand Up @@ -1334,3 +1334,22 @@ def jinja2_environment_from_request(self, request, env):
assert "Hello museums!" in response2.text
finally:
pm.unregister(name="EnvironmentPlugin")


@pytest.mark.asyncio
async def test_hook_top_homepage():
class HookPlugin:
__name__ = "HookPlugin"

@hookimpl
def top_homepage(self, request):
return "XXX-YYY: " + request.args["z"]

try:
pm.register(HookPlugin(), name="HookPlugin")
datasette = Datasette(memory=True)
response = await datasette.client.get("/?z=foo")
assert response.status_code == 200
assert "XXX-YYY: foo" in response.text
finally:
pm.unregister(name="HookPlugin")

0 comments on commit 0c814ce

Please sign in to comment.