Optional path parameters for custom pages, closes #944

pull/977/head
Simon Willison 2020-09-13 19:33:55 -07:00
rodzic ea340cf320
commit cc77fcd133
3 zmienionych plików z 72 dodań i 3 usunięć

Wyświetl plik

@ -942,6 +942,16 @@ class DatasetteRouter:
((re.compile(pattern) if isinstance(pattern, str) else pattern), view)
for pattern, view in routes
]
# Build a list of pages/blah/{name}.html matching expressions
pattern_templates = [
filepath
for filepath in self.ds.jinja_env.list_templates()
if "{" in filepath and filepath.startswith("pages/")
]
self.page_routes = [
(route_pattern_from_filepath(filepath[len("pages/") :]), filepath)
for filepath in pattern_templates
]
async def __call__(self, scope, receive, send):
# Because we care about "foo/bar" v.s. "foo%2Fbar" we decode raw_path ourselves
@ -1002,6 +1012,7 @@ class DatasetteRouter:
async def handle_404(self, request, send, exception=None):
# If URL has a trailing slash, redirect to URL without it
path = request.scope.get("raw_path", request.scope["path"].encode("utf8"))
context = {}
if path.endswith(b"/"):
path = path.rstrip(b"/")
if request.scope["query_string"]:
@ -1016,6 +1027,15 @@ class DatasetteRouter:
template = self.ds.jinja_env.select_template([template_path])
except TemplateNotFound:
template = None
if template is None:
# Try for a pages/blah/{name}.html template match
for regex, wildcard_template in self.page_routes:
match = regex.match(request.scope["path"])
if match is not None:
context.update(match.groupdict())
template = wildcard_template
break
if template:
headers = {}
status = [200]
@ -1033,13 +1053,16 @@ class DatasetteRouter:
headers["Location"] = location
return ""
body = await self.ds.render_template(
template,
context.update(
{
"custom_header": custom_header,
"custom_status": custom_status,
"custom_redirect": custom_redirect,
},
}
)
body = await self.ds.render_template(
template,
context,
request=request,
view_name="page",
)
@ -1160,3 +1183,19 @@ def wrap_view(view_fn, datasette):
return response
return async_view_fn
_curly_re = re.compile("(\{.*?\})")
def route_pattern_from_filepath(filepath):
# Drop the ".html" suffix
if filepath.endswith(".html"):
filepath = filepath[: -len(".html")]
re_bits = ["/"]
for bit in _curly_re.split(filepath):
if _curly_re.match(bit):
re_bits.append("(?P<{}>[^/]*)".format(bit[1:-1]))
else:
re_bits.append(re.escape(bit))
return re.compile("".join(re_bits))

Wyświetl plik

@ -281,6 +281,25 @@ For example, to add a custom page that is served at ``http://localhost/about`` y
You can nest directories within pages to create a nested structure. To create a ``http://localhost:8001/about/map`` page you would create ``templates/pages/about/map.html``.
.. _custom_pages_parameters:
Path parameters for pages
~~~~~~~~~~~~~~~~~~~~~~~~~
You can define custom pages that match multiple paths by creating files with ``{variable}`` definitions in their filenames.
For example, to capture any request to a URL matching ``/about/*``, you would create a template in the following location::
templates/pages/about/{slug}.html
A hit to ``/about/news`` would render that template and pass in a variable called ``slug`` with a value of ``"news"``.
If you use this mechanism don't forget to return a 404 status code if the page should not be considered a valid page. You can do this using ``{{ custom_status(404) }}`` described below.
Templates defined using custom page routes work particularly well with the ``sql()`` template function from `datasette-template-sql <https://github.com/simonw/datasette-template-sql>`__ or the ``graphql()`` template function from `datasette-graphql <https://github.com/simonw/datasette-graphql#the-graphql-template-function>`__.
.. _custom_pages_headers:
Custom headers and status codes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -316,6 +335,8 @@ You can verify this is working using ``curl`` like this::
x-teapot: I am
content-type: text/html; charset=utf-8
.. _custom_pages_redirects:
Custom redirects
~~~~~~~~~~~~~~~~

Wyświetl plik

@ -25,6 +25,9 @@ def custom_pages_client(tmp_path_factory):
(pages_dir / "redirect2.html").write_text(
'{{ custom_redirect("/example", 301) }}', "utf-8"
)
(pages_dir / "route_{name}.html").write_text(
"<p>Hello from {{ name }}</p>", "utf-8"
)
nested_dir = pages_dir / "nested"
nested_dir.mkdir()
(nested_dir / "nest.html").write_text("Nest!", "utf-8")
@ -83,3 +86,9 @@ def test_redirect2(custom_pages_client):
response = custom_pages_client.get("/redirect2", allow_redirects=False)
assert 301 == response.status
assert "/example" == response.headers["Location"]
def test_custom_route_pattern(custom_pages_client):
response = custom_pages_client.get("/route_Sally")
assert response.status == 200
assert response.text == "<p>Hello from Sally</p>"