aurweb/aurweb/templates.py
Steven Guikal a10f8663fd fix(FastAPI): reorganize credential checkin into dedicated file
Signed-off-by: Steven Guikal <void@fluix.one>
2021-12-01 02:03:02 -05:00

165 lines
5.3 KiB
Python

import copy
import functools
import math
import os
import zoneinfo
from datetime import datetime
from http import HTTPStatus
from typing import Callable
from urllib.parse import quote_plus
import jinja2
from fastapi import Request
from fastapi.responses import HTMLResponse
import aurweb.config
from aurweb import auth, captcha, cookies, l10n, time, util
# Prepare jinja2 objects.
_loader = jinja2.FileSystemLoader(os.path.join(
aurweb.config.get("options", "aurwebdir"), "templates"))
_env = jinja2.Environment(loader=_loader, autoescape=True,
extensions=["jinja2.ext.i18n"])
# Add t{r,n} translation filters.
_env.filters["tr"] = l10n.tr
_env.filters["tn"] = l10n.tn
# Utility filters.
_env.filters["dt"] = util.timestamp_to_datetime
_env.filters["as_timezone"] = util.as_timezone
_env.filters["extend_query"] = util.extend_query
_env.filters["urlencode"] = util.to_qs
_env.filters["quote_plus"] = quote_plus
_env.filters["get_vote"] = util.get_vote
_env.filters["number_format"] = util.number_format
_env.filters["ceil"] = math.ceil
# Add captcha filters.
_env.filters["captcha_salt"] = captcha.captcha_salt_filter
_env.filters["captcha_cmdline"] = captcha.captcha_cmdline_filter
# Add account utility filters.
_env.filters["account_url"] = util.account_url
def register_filter(name: str) -> Callable:
""" A decorator that can be used to register a filter.
Example
@register_filter("some_filter")
def some_filter(some_value: str) -> str:
return some_value.replace("-", "_")
Jinja2
{{ 'blah-blah' | some_filter }}
:param name: Filter name
:return: Callable used for filter
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
if name in _env.filters:
raise KeyError(f"Jinja already has a filter named '{name}'")
_env.filters[name] = wrapper
return wrapper
return decorator
def register_function(name: str) -> Callable:
""" A decorator that can be used to register a function.
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
if name in _env.globals:
raise KeyError(f"Jinja already has a function named '{name}'")
_env.globals[name] = wrapper
return wrapper
return decorator
def make_context(request: Request, title: str, next: str = None):
""" Create a context for a jinja2 TemplateResponse. """
commit_url = aurweb.config.get_with_fallback("devel", "commit_url", None)
commit_hash = aurweb.config.get_with_fallback("devel", "commit_hash", None)
if commit_hash:
# Shorten commit_hash to a short Git hash.
commit_hash = commit_hash[:7]
timezone = time.get_request_timezone(request)
return {
"request": request,
"commit_url": commit_url,
"commit_hash": commit_hash,
"language": l10n.get_request_language(request),
"languages": l10n.SUPPORTED_LANGUAGES,
"timezone": timezone,
"timezones": time.SUPPORTED_TIMEZONES,
"title": title,
"now": datetime.now(tz=zoneinfo.ZoneInfo(timezone)),
"utcnow": int(datetime.utcnow().timestamp()),
"config": aurweb.config,
"creds": auth.creds,
"next": next if next else request.url.path
}
async def make_variable_context(request: Request, title: str, next: str = None):
""" Make a context with variables provided by the user
(query params via GET or form data via POST). """
context = make_context(request, title, next)
to_copy = dict(request.query_params) \
if request.method.lower() == "get" \
else dict(await request.form())
for k, v in to_copy.items():
context[k] = v
return context
def base_template(path: str):
templates = copy.copy(_env)
return templates.get_template(path)
def render_raw_template(request: Request, path: str, context: dict):
""" Render a Jinja2 multi-lingual template with some context. """
# Create a deep copy of our jinja2 _environment. The _environment in
# total by itself is 48 bytes large (according to sys.getsizeof).
# This is done so we can install gettext translations on the template
# _environment being rendered without installing them into a global
# which is reused in this function.
templates = copy.copy(_env)
translator = l10n.get_raw_translator_for_request(context.get("request"))
templates.install_gettext_translations(translator)
template = templates.get_template(path)
return template.render(context)
def render_template(request: Request,
path: str,
context: dict,
status_code: HTTPStatus = HTTPStatus.OK):
""" Render a template as an HTMLResponse. """
rendered = render_raw_template(request, path, context)
response = HTMLResponse(rendered, status_code=int(status_code))
sid = None
if request.user.is_authenticated():
sid = request.cookies.get("AURSID")
# Re-emit SID via update_response_cookies with an updated expiration.
# This extends the life of a user session based on the AURREMEMBER
# cookie, which is always set to the "Remember Me" state on login.
return cookies.update_response_cookies(request, response, aursid=sid)