From 5d302ae00c89aa2537f48959b3d96c6a9d835046 Mon Sep 17 00:00:00 2001 From: moson Date: Thu, 19 Oct 2023 18:41:06 +0200 Subject: [PATCH] feat: Support timezone and language query params Support setting the timezone as well as the language via query params: The timezone parameter previously only worked on certain pages. While we're at it, let's also add the language as a param. Refactor code for timezone and language functions. Remove unused AURTZ cookie. Signed-off-by: moson --- aurweb/l10n.py | 21 ++++++++++++++++---- aurweb/templates.py | 10 ++-------- aurweb/time.py | 19 ++++++++++-------- test/test_l10n.py | 48 ++++++++++++++++++++++++++++++++++++++++++--- test/test_time.py | 24 +++++++++++++---------- 5 files changed, 89 insertions(+), 33 deletions(-) diff --git a/aurweb/l10n.py b/aurweb/l10n.py index 2ef2c43e..1ce5e956 100644 --- a/aurweb/l10n.py +++ b/aurweb/l10n.py @@ -64,11 +64,24 @@ class Translator: translator = Translator() -def get_request_language(request: Request): - if request.user.is_authenticated(): +def get_request_language(request: Request) -> str: + """Get a request's language from either query param, user setting or + cookie. We use the configuration's [options] default_lang otherwise. + + @param request FastAPI request + """ + request_lang = request.query_params.get("language") + cookie_lang = request.cookies.get("AURLANG") + if request_lang and request_lang in SUPPORTED_LANGUAGES: + return request_lang + elif ( + request.user.is_authenticated() + and request.user.LangPreference in SUPPORTED_LANGUAGES + ): return request.user.LangPreference - default_lang = aurweb.config.get("options", "default_lang") - return request.cookies.get("AURLANG", default_lang) + elif cookie_lang and cookie_lang in SUPPORTED_LANGUAGES: + return cookie_lang + return aurweb.config.get_with_fallback("options", "default_lang", "en") def get_raw_translator_for_request(request: Request): diff --git a/aurweb/templates.py b/aurweb/templates.py index 1fbb7f21..9e18014c 100644 --- a/aurweb/templates.py +++ b/aurweb/templates.py @@ -3,7 +3,6 @@ import functools import os from http import HTTPStatus from typing import Callable -from zoneinfo import ZoneInfoNotFoundError import jinja2 from fastapi import Request @@ -75,10 +74,7 @@ def make_context(request: Request, title: str, next: str = None): # Shorten commit_hash to a short Git hash. commit_hash = commit_hash[:7] - try: - timezone = time.get_request_timezone(request) - except ZoneInfoNotFoundError: - timezone = DEFAULT_TIMEZONE + timezone = time.get_request_timezone(request) language = l10n.get_request_language(request) return { "request": request, @@ -110,9 +106,7 @@ async def make_variable_context(request: Request, title: str, next: str = None): ) for k, v in to_copy.items(): - if k == "timezone": - context[k] = v if v in time.SUPPORTED_TIMEZONES else DEFAULT_TIMEZONE - elif k not in context: + if k not in context: context[k] = v context["q"] = dict(request.query_params) diff --git a/aurweb/time.py b/aurweb/time.py index 505f17f5..1e98b0d8 100644 --- a/aurweb/time.py +++ b/aurweb/time.py @@ -1,7 +1,6 @@ import zoneinfo from collections import OrderedDict from datetime import datetime -from urllib.parse import unquote from zoneinfo import ZoneInfo from fastapi import Request @@ -58,16 +57,20 @@ SUPPORTED_TIMEZONES = OrderedDict( ) -def get_request_timezone(request: Request): - """Get a request's timezone by its AURTZ cookie. We use the - configuration's [options] default_timezone otherwise. +def get_request_timezone(request: Request) -> str: + """Get a request's timezone from either query param or user settings. + We use the configuration's [options] default_timezone otherwise. @param request FastAPI request """ - default_tz = aurweb.config.get("options", "default_timezone") - if request.user.is_authenticated(): - default_tz = request.user.Timezone - return unquote(request.cookies.get("AURTZ", default_tz)) + request_tz = request.query_params.get("timezone") + if request_tz and request_tz in SUPPORTED_TIMEZONES: + return request_tz + elif ( + request.user.is_authenticated() and request.user.Timezone in SUPPORTED_TIMEZONES + ): + return request.user.Timezone + return aurweb.config.get_with_fallback("options", "default_timezone", "UTC") def now(timezone: str) -> datetime: diff --git a/test/test_l10n.py b/test/test_l10n.py index 818d517f..b86e78fc 100644 --- a/test/test_l10n.py +++ b/test/test_l10n.py @@ -1,5 +1,5 @@ """ Test our l10n module. """ -from aurweb import filters, l10n +from aurweb import config, filters, l10n from aurweb.testing.requests import Request @@ -10,13 +10,55 @@ def test_translator(): def test_get_request_language(): - """First, tests default_lang, then tests a modified AURLANG cookie.""" + """Test getting the language setting from a request.""" + # Default language + default_lang = config.get("options", "default_lang") request = Request() - assert l10n.get_request_language(request) == "en" + assert l10n.get_request_language(request) == default_lang + # Language setting from cookie: de request.cookies["AURLANG"] = "de" assert l10n.get_request_language(request) == "de" + # Language setting from cookie: nonsense + # Should fallback to default lang + request.cookies["AURLANG"] = "nonsense" + assert l10n.get_request_language(request) == default_lang + + # Language setting from query param: de + request.cookies = {} + request.query_params = {"language": "de"} + assert l10n.get_request_language(request) == "de" + + # Language setting from query param: nonsense + # Should fallback to default lang + request.query_params = {"language": "nonsense"} + assert l10n.get_request_language(request) == default_lang + + # Language setting from query param: de and cookie + # Query param should have precedence + request.query_params = {"language": "de"} + request.cookies["AURLANG"] = "fr" + assert l10n.get_request_language(request) == "de" + + # Language setting from authenticated user + request.cookies = {} + request.query_params = {} + request.user.authenticated = True + request.user.LangPreference = "de" + assert l10n.get_request_language(request) == "de" + + # Language setting from authenticated user with query param + # Query param should have precedence + request.query_params = {"language": "fr"} + assert l10n.get_request_language(request) == "fr" + + # Language setting from authenticated user with cookie + # DB setting should have precedence + request.query_params = {} + request.cookies["AURLANG"] = "fr" + assert l10n.get_request_language(request) == "de" + def test_get_raw_translator_for_request(): """Make sure that get_raw_translator_for_request is giving us diff --git a/test/test_time.py b/test/test_time.py index db7b30bf..45328717 100644 --- a/test/test_time.py +++ b/test/test_time.py @@ -15,18 +15,22 @@ def test_tz_offset_mst(): def test_request_timezone(): request = Request() - tz = get_request_timezone(request) - assert tz == aurweb.config.get("options", "default_timezone") + # Default timezone + dtz = aurweb.config.get("options", "default_timezone") + assert get_request_timezone(request) == dtz -def test_authenticated_request_timezone(): - # Modify a fake request to be authenticated with the - # America/Los_Angeles timezone. - request = Request() + # Timezone from query params + request.query_params = {"timezone": "Europe/Berlin"} + assert get_request_timezone(request) == "Europe/Berlin" + + # Timezone from authenticated user. + request.query_params = {} request.user.authenticated = True request.user.Timezone = "America/Los_Angeles" + assert get_request_timezone(request) == "America/Los_Angeles" - # Get the request's timezone, it should be America/Los_Angeles. - tz = get_request_timezone(request) - assert tz == request.user.Timezone - assert tz == "America/Los_Angeles" + # Timezone from authenticated user with query param + # Query param should have precedence + request.query_params = {"timezone": "Europe/Berlin"} + assert get_request_timezone(request) == "Europe/Berlin"