kopia lustrzana https://github.com/simonw/datasette
Optional path parameters for custom pages, closes #944
rodzic
ea340cf320
commit
cc77fcd133
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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>"
|
||||
|
|
Ładowanie…
Reference in New Issue