You've already forked snikket-web-portal
149 lines
4.0 KiB
Python
149 lines
4.0 KiB
Python
import base64
|
||
import itertools
|
||
import math
|
||
import secrets
|
||
import typing
|
||
|
||
from datetime import datetime, timedelta, timezone
|
||
|
||
import quart.flask_patch # noqa:F401
|
||
from quart import (
|
||
current_app,
|
||
request,
|
||
g,
|
||
)
|
||
|
||
import flask_babel
|
||
import flask_wtf
|
||
from flask_babel import lazy_gettext as _l
|
||
import flask_babel as _
|
||
|
||
from . import prosodyclient
|
||
|
||
|
||
client = prosodyclient.ProsodyClient()
|
||
client.default_login_redirect = "main.login"
|
||
|
||
babel = flask_babel.Babel()
|
||
|
||
|
||
BYTE_UNIT_SCALE_MAP = [
|
||
"B",
|
||
"kiB",
|
||
"MiB",
|
||
"GiB",
|
||
"TiB",
|
||
]
|
||
|
||
|
||
@babel.localeselector # type:ignore
|
||
def selected_locale() -> str:
|
||
g.language_header_accessed = True
|
||
selected = request.accept_languages.best_match(
|
||
current_app.config['LANGUAGES']
|
||
) or current_app.config['LANGUAGES'][0]
|
||
return selected
|
||
|
||
|
||
def flatten(a: typing.Iterable, levels: int = 1) -> typing.Iterable:
|
||
for i in range(levels):
|
||
a = itertools.chain(*a)
|
||
return a
|
||
|
||
|
||
def circle_name(c: typing.Any) -> str:
|
||
if c.id_ == "default" and c.name == "default":
|
||
return _("Main")
|
||
return c.name
|
||
|
||
|
||
def format_bytes(n: float) -> str:
|
||
try:
|
||
scale = max(math.floor(math.log(n, 1024)), 0)
|
||
except ValueError:
|
||
scale = 0
|
||
try:
|
||
unit = BYTE_UNIT_SCALE_MAP[scale]
|
||
factor = 1024**scale
|
||
except IndexError:
|
||
unit = "TiB"
|
||
factor = 1024**4
|
||
if factor > 1:
|
||
return "{:.1f} {}".format(n / factor, unit)
|
||
return "{} {}".format(n, unit)
|
||
|
||
|
||
def format_last_activity(timestamp: typing.Optional[int]) -> str:
|
||
if timestamp is None:
|
||
return _l("Never")
|
||
|
||
last_active = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
||
# TODO: This 'now' should use the user's local time zone, but we
|
||
# don't have that information. Thus 'today'/'yesterday' may be
|
||
# slightly inaccurate, but compared to alternative solutions it
|
||
# should hopefully be "good enough".
|
||
now = datetime.now(tz=timezone.utc)
|
||
time_ago = now - last_active
|
||
|
||
yesterday = now - timedelta(days=1)
|
||
|
||
if (
|
||
last_active.year == now.year
|
||
and last_active.month == now.month
|
||
and last_active.day == now.day
|
||
):
|
||
return _l("Today")
|
||
elif (
|
||
last_active.year == yesterday.year
|
||
and last_active.month == yesterday.month
|
||
and last_active.day == yesterday.day
|
||
):
|
||
return _l("Yesterday")
|
||
|
||
return _.gettext(
|
||
"%(time)s ago",
|
||
time=flask_babel.format_timedelta(time_ago, granularity="day"),
|
||
)
|
||
|
||
|
||
def template_now() -> typing.Dict[str, typing.Any]:
|
||
return dict(now=lambda: datetime.now(timezone.utc))
|
||
|
||
|
||
def add_vary_language_header(resp: quart.Response) -> quart.Response:
|
||
if getattr(g, "language_header_accessed", False):
|
||
resp.vary.add("Accept-Language")
|
||
return resp
|
||
|
||
|
||
def init_templating(app: quart.Quart) -> None:
|
||
app.template_filter("repr")(repr)
|
||
app.template_filter("format_datetime")(flask_babel.format_datetime)
|
||
app.template_filter("format_date")(flask_babel.format_date)
|
||
app.template_filter("format_time")(flask_babel.format_time)
|
||
app.template_filter("format_timedelta")(flask_babel.format_timedelta)
|
||
app.template_filter("format_percent")(flask_babel.format_percent)
|
||
app.template_filter("format_bytes")(format_bytes)
|
||
app.template_filter("flatten")(flatten)
|
||
app.template_filter("circle_name")(circle_name)
|
||
app.template_filter("format_last_activity")(format_last_activity)
|
||
app.context_processor(template_now)
|
||
app.after_request(add_vary_language_header)
|
||
|
||
|
||
def generate_error_id() -> str:
|
||
return base64.b32encode(secrets.token_bytes(8)).decode(
|
||
"ascii"
|
||
).rstrip("=")
|
||
|
||
|
||
class BaseForm(flask_wtf.FlaskForm): # type:ignore
|
||
def __init__(self, *args: typing.Any, **kwargs: typing.Any):
|
||
meta = kwargs["meta"] = dict(kwargs.get("meta", {}))
|
||
if "locales" not in meta:
|
||
locale = flask_babel.get_locale()
|
||
if locale:
|
||
meta["locales"] = [str(locale)]
|
||
|
||
super().__init__(*args, **kwargs)
|