diff --git a/datasette/app.py b/datasette/app.py index 354cf5e2..baddfc9f 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -762,10 +762,42 @@ class DatasetteRouter(AsgiRouter): except TemplateNotFound: template = None if template: - await asgi_send_html( + headers = {} + status = [200] + + def custom_header(name, value): + headers[name] = value + return "" + + def custom_status(code): + status[0] = code + return "" + + def custom_redirect(location, code=302): + status[0] = code + headers["Location"] = location + return "" + + body = await self.ds.render_template( + template, + { + "custom_header": custom_header, + "custom_status": custom_status, + "custom_redirect": custom_redirect, + }, + view_name="page", + ) + # Pull content-type out into separate parameter + content_type = "text/html" + matches = [k for k in headers if k.lower() == "content-type"] + if matches: + content_type = headers[matches[0]] + await asgi_send( send, - await self.ds.render_template(template, view_name="page"), - status=200, + body, + status=status[0], + headers=headers, + content_type=content_type, ) else: await self.handle_500( diff --git a/tests/test_custom_pages.py b/tests/test_custom_pages.py new file mode 100644 index 00000000..bd2c427b --- /dev/null +++ b/tests/test_custom_pages.py @@ -0,0 +1,80 @@ +import pytest +from .fixtures import make_app_client + +VIEW_NAME_PLUGIN = """ +from datasette import hookimpl + +@hookimpl +def extra_template_vars(view_name): + return { + "view_name": view_name, + } +""" + + +@pytest.fixture(scope="session") +def custom_pages_client(tmp_path_factory): + template_dir = tmp_path_factory.mktemp("page-templates") + extra_plugins = {"view_name.py": VIEW_NAME_PLUGIN} + pages_dir = template_dir / "pages" + pages_dir.mkdir() + (pages_dir / "about.html").write_text("ABOUT! view_name:{{ view_name }}", "utf-8") + (pages_dir / "202.html").write_text("{{ custom_status(202) }}202!", "utf-8") + (pages_dir / "headers.html").write_text( + '{{ custom_header("x-this-is-foo", "foo") }}FOO' + '{{ custom_header("x-this-is-bar", "bar") }}BAR', + "utf-8", + ) + (pages_dir / "redirect.html").write_text( + '{{ custom_redirect("/example") }}', "utf-8" + ) + (pages_dir / "redirect2.html").write_text( + '{{ custom_redirect("/example", 301) }}', "utf-8" + ) + nested_dir = pages_dir / "nested" + nested_dir.mkdir() + (nested_dir / "nest.html").write_text("Nest!", "utf-8") + for client in make_app_client( + template_dir=str(template_dir), extra_plugins=extra_plugins + ): + yield client + + +def test_custom_pages_view_name(custom_pages_client): + response = custom_pages_client.get("/about") + assert 200 == response.status + assert "ABOUT! view_name:page" == response.text + + +def test_custom_pages_nested(custom_pages_client): + response = custom_pages_client.get("/nested/nest") + assert 200 == response.status + assert "Nest!" == response.text + response = custom_pages_client.get("/nested/nest2") + assert 404 == response.status + + +def test_custom_status(custom_pages_client): + response = custom_pages_client.get("/202") + assert 202 == response.status + assert "202!" == response.text + + +def test_custom_headers(custom_pages_client): + response = custom_pages_client.get("/headers") + assert 200 == response.status + assert "foo" == response.headers["x-this-is-foo"] + assert "bar" == response.headers["x-this-is-bar"] + assert "FOOBAR" == response.text + + +def test_redirect(custom_pages_client): + response = custom_pages_client.get("/redirect", allow_redirects=False) + assert 302 == response.status + assert "/example" == response.headers["Location"] + + +def test_redirect2(custom_pages_client): + response = custom_pages_client.get("/redirect2", allow_redirects=False) + assert 301 == response.status + assert "/example" == response.headers["Location"] diff --git a/tests/test_html.py b/tests/test_html.py index 10ed8e28..b8dc543c 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1242,35 +1242,3 @@ def test_base_url_config(base_url, path): "href_or_src": href, "element_parent": str(el.parent), } - - -def test_custom_pages(tmpdir): - template_dir = tmpdir.mkdir("page-templates") - extra_plugins = { - "view_name.py": textwrap.dedent( - """ - from datasette import hookimpl - - @hookimpl - def extra_template_vars(view_name): - return { - "view_name": view_name, - } - """ - ) - } - pages_dir = template_dir.mkdir("pages") - (pages_dir / "about.html").write_text("ABOUT! view_name:{{ view_name }}", "utf-8") - nested_dir = pages_dir.mkdir("nested") - (nested_dir / "nest.html").write_text("Nest!", "utf-8") - for client in make_app_client( - template_dir=str(template_dir), extra_plugins=extra_plugins - ): - response = client.get("/about") - assert 200 == response.status - assert "ABOUT! view_name:page" == response.text - response = client.get("/nested/nest") - assert 200 == response.status - assert "Nest!" == response.text - response = client.get("/nested/nest2") - assert 404 == response.status