Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Jinja dependency for landing page generation #1082

Merged
merged 3 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 66 additions & 17 deletions optimade/server/routers/landing.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,88 @@
""" OPTIMADE landing page, rendered as a Jinja2 template. """
""" OPTIMADE landing page router. """

from pathlib import Path
from functools import lru_cache

from fastapi import Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
from starlette.routing import Router, Route
from optimade import __api_version__

from optimade.server.routers import ENTRY_COLLECTIONS
from optimade.server.routers.utils import meta_values, get_base_url
from optimade.server.config import CONFIG

template_dir = Path(__file__).parent.joinpath("static").resolve()
TEMPLATES = Jinja2Templates(directory=[template_dir])

@lru_cache()
def render_landing_page(url: str) -> HTMLResponse:
"""Render and cache the landing page.

async def landing(request: Request):
"""Show a human-readable landing page when the base URL is accessed."""
This function uses the template file `./static/landing_page.html`, adapted
from the original Jinja template. Instead of Jinja, some basic string
replacement is used to fill out the fields from the server configuration.

meta = meta_values(request.url, 1, 1, more_data_available=False)
!! warning
The removal of Jinja means that the fields are no longer validated as
web safe before inclusion in the template.
ml-evs marked this conversation as resolved.
Show resolved Hide resolved

"""
meta = meta_values(url, 1, 1, more_data_available=False)
major_version = __api_version__.split(".")[0]
versioned_url = f"{get_base_url(request.url)}/v{major_version}/"
versioned_url = f"{get_base_url(url)}/v{major_version}/"

template_dir = Path(__file__).parent.joinpath("static").resolve()

context = {
"request": request,
"request_url": request.url,
with open(template_dir / "landing_page.html", "r") as f:
html = "\n".join(f.readlines())
ml-evs marked this conversation as resolved.
Show resolved Hide resolved

# Build a dictionary that maps the old Jinja keys to the new simplified replacements
replacements: dict[str, str] = {
ml-evs marked this conversation as resolved.
Show resolved Hide resolved
"api_version": __api_version__,
"implementation": meta.implementation,
"versioned_url": versioned_url,
"provider": meta.provider,
"index_base_url": CONFIG.index_base_url,
"endpoints": list(ENTRY_COLLECTIONS.keys()) + ["info"],
}

return TEMPLATES.TemplateResponse("landing_page.html", context)
if meta.provider:
replacements.update(
{
"provider.name": meta.provider.name,
"provider.prefix": meta.provider.prefix,
"provider.description": meta.provider.description,
"provider.homepage": str(meta.provider.homepage) or "",
}
)

if meta.implementation:
replacements.update(
{
"implementation.name": meta.implementation.name or "",
"implementation.version": meta.implementation.version or "",
"implementation.source_url": str(meta.implementation.source_url or ""),
}
)

for replacement in replacements:
html = html.replace(f"{{{{ {replacement} }}}}", replacements[replacement])

# Build the list of endpoints. The template already opens and closes the `<ul>` tag.
endpoints_list = [
f'<li><a href="{versioned_url}{endp}">{versioned_url}{endp}'
for endp in list(ENTRY_COLLECTIONS.keys()) + ["info"]
]
html = html.replace("{% ENDPOINTS %}", "\n".join(endpoints_list))

# If the index base URL has been configured, also list it
index_base_url_html = ""
if CONFIG.index_base_url:
index_base_url_html = f"""<h3>Index base URL:</h3>
<p><a href={CONFIG.index_base_url}>{CONFIG.index_base_url}</a></p>
"""
html = html.replace("{% INDEX_BASE_URL %}", index_base_url_html)

return HTMLResponse(html)


async def landing(request: Request):
"""Show a human-readable landing page when the base URL is accessed."""
return render_landing_page(str(request.url))


router = Router(routes=[Route("/", endpoint=landing)])
13 changes: 3 additions & 10 deletions optimade/server/routers/static/landing_page.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<title>{{ optimade_title }}</title>
<title>{{ provider.name }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="optimade,materials,crystals,database">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
{% endblock %}

<style>
html { margin: 2em; }
Expand All @@ -31,14 +29,9 @@ <h4>{{ implementation.name }}</h4>
<p><a href={{ implementation.source_url }}>{{ implementation.source_url }}</a></p>
<h3>Available endpoints:</h3>
<ul>
{% for endpoint in endpoints %}
{{ '<li><a href="{}{}">{}{}</a></li>'.format(versioned_url, endpoint, versioned_url, endpoint) | safe }}
{% endfor %}
{% ENDPOINTS %}
</ul>
{% if index_base_url != None %}
<h3>Index base URL:</h3>
<p><a href={{ index_base_url }}>{{ index_base_url }}</a></p>
{% endif %}
{% INDEX_BASE_URL %}
</body>
<footer>
<div class="footer_text">
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
elasticsearch-dsl==6.4.0
email_validator==1.1.3
fastapi==0.65.2
Jinja2==3.0.3
lark-parser==0.12.0
mongomock==4.0.0
pydantic==1.9.0
Expand Down