From b9ddf41958485c067a8768d193e7993414c0f168 Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 26 Aug 2026 13:49:31 +0100 Subject: [PATCH 1/2] Fix UI language not persisting beyond the request that set it (server mode) get_locale() used setattr()/hasattr() against Flask's session and the request's cookie MultiDict, neither of which expose their contents as attributes, so the session/cookie fallback branches never actually fired. Read and write PGADMIN_LANGUAGE via dict-style access instead, so the selected language persists across requests via the session and the PGADMIN_LANGUAGE cookie that is already set correctly elsewhere. Closes #10347 --- web/pgadmin/__init__.py | 12 +++---- web/pgadmin/tests/__init__.py | 8 +++++ web/pgadmin/tests/test_get_locale.py | 54 ++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 web/pgadmin/tests/__init__.py create mode 100644 web/pgadmin/tests/test_get_locale.py diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py index ceff7aaabec..696f982168b 100644 --- a/web/pgadmin/__init__.py +++ b/web/pgadmin/__init__.py @@ -328,12 +328,12 @@ def get_locale(): data = request.form if 'language' in data: language = data['language'] or language - setattr(session, 'PGADMIN_LANGUAGE', language) - elif hasattr(session, 'PGADMIN_LANGUAGE'): - language = getattr(session, 'PGADMIN_LANGUAGE', language) - elif hasattr(request.cookies, 'PGADMIN_LANGUAGE'): - language = getattr( - request.cookies, 'PGADMIN_LANGUAGE', language + session['PGADMIN_LANGUAGE'] = language + elif 'PGADMIN_LANGUAGE' in session: + language = session.get('PGADMIN_LANGUAGE', language) + elif 'PGADMIN_LANGUAGE' in request.cookies: + language = request.cookies.get( + 'PGADMIN_LANGUAGE', language ) return language diff --git a/web/pgadmin/tests/__init__.py b/web/pgadmin/tests/__init__.py new file mode 100644 index 00000000000..c69ae19d4a5 --- /dev/null +++ b/web/pgadmin/tests/__init__.py @@ -0,0 +1,8 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2026, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## diff --git a/web/pgadmin/tests/test_get_locale.py b/web/pgadmin/tests/test_get_locale.py new file mode 100644 index 00000000000..ed72ad57a81 --- /dev/null +++ b/web/pgadmin/tests/test_get_locale.py @@ -0,0 +1,54 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2026, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Verify that the Babel locale_selector registered in create_app() reads +the selected UI language back from the session and cookie on requests that +don't include the 'language' form field (issue #10347). +""" + +import config +from pgadmin.utils.route import BaseTestGenerator + + +class GetLocaleTestCase(BaseTestGenerator): + """Exercises pgadmin.__init__.create_app()'s get_locale() directly via + the Babel extension, bypassing the need for a database connection. + """ + + # No server interaction needed, so skip BaseTestGenerator.setUp's + # connect_server(). + def setUp(self): + self._orig_server_mode = config.SERVER_MODE + config.SERVER_MODE = True + + def tearDown(self): + config.SERVER_MODE = self._orig_server_mode + + def _get_locale(self): + return self.app.extensions['babel'].locale_selector() + + def runTest(self): + # The 'language' form field sets the language for this request and + # must persist it to the session for subsequent requests. + from flask import session + with self.app.test_request_context( + '/', method='POST', data={'language': 'fr'}): + self.assertEqual(self._get_locale(), 'fr') + self.assertEqual(session.get('PGADMIN_LANGUAGE'), 'fr') + + # A request with no 'language' field but an existing session value + # must keep using that language. + with self.app.test_request_context('/'): + session['PGADMIN_LANGUAGE'] = 'de' + self.assertEqual(self._get_locale(), 'de') + + # With no session value, the PGADMIN_LANGUAGE cookie must be read. + with self.app.test_request_context( + '/', headers={'Cookie': 'PGADMIN_LANGUAGE=it'}): + self.assertEqual(self._get_locale(), 'it') From 2b29ecdd8bff1eb52bbeb554963da85d45d5b419 Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 26 Aug 2026 14:12:23 +0100 Subject: [PATCH 2/2] Fix the same session-attribute bug in preferences language save setattr(session, ...) here has the identical problem fixed in get_locale(): it sets a plain instance attribute rather than a session dict entry, so it never reaches the serialised session. Use dict-style assignment instead, per CodeRabbit's review on PR #10351. --- web/pgadmin/preferences/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/pgadmin/preferences/__init__.py b/web/pgadmin/preferences/__init__.py index 66152b31ad2..4dec271d2de 100644 --- a/web/pgadmin/preferences/__init__.py +++ b/web/pgadmin/preferences/__init__.py @@ -289,7 +289,7 @@ def save(): config.COOKIE_DEFAULT_DOMAIN != 'localhost': domain['domain'] = config.COOKIE_DEFAULT_DOMAIN - setattr(session, 'PGADMIN_LANGUAGE', language) + session['PGADMIN_LANGUAGE'] = language response.set_cookie("PGADMIN_LANGUAGE", value=language, path=config.SESSION_COOKIE_PATH, secure=config.SESSION_COOKIE_SECURE,