diff --git a/addons/test_event_full/tests/test_performance.py b/addons/test_event_full/tests/test_performance.py
index 0bc17efb951..76e2e808847 100644
--- a/addons/test_event_full/tests/test_performance.py
+++ b/addons/test_event_full/tests/test_performance.py
@@ -52,7 +52,7 @@ class TestEventPerformance(EventPerformanceCase):
batch_size = 20
# simple without type involved + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=3236): # tef 3173 / com 3172
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=3377): # tef 3275 / com 3274
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -60,7 +60,7 @@ class TestEventPerformance(EventPerformanceCase):
)
for x in range(batch_size)
]
- self.env['event.event'].create(event_values)
+ self.env['event.event'].with_context(lang='en_US').create(event_values)
@users('event_user')
@warmup
@@ -89,7 +89,7 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=3340): # tef 3277 / com 3276
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=3481): # tef 3379 / com 3378
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -97,7 +97,7 @@ class TestEventPerformance(EventPerformanceCase):
)
for x in range(batch_size)
]
- self.env['event.event'].create(event_values)
+ self.env['event.event'].with_context(lang='en_US').create(event_values)
@users('event_user')
@@ -128,7 +128,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# no type, website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=427): # tef 379 / com 380
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=447): # tef 379 / com 427
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
#
@@ -150,7 +150,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# type and website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=450): # tef 402 / com 408
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=470): # tef 402 / com 450
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
#
@@ -181,13 +181,13 @@ class TestEventPerformance(EventPerformanceCase):
def test_event_create_single_notype_website(self):
""" Test a single event creation """
# simple without type involved + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=234): # tef 228 / com 227
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=242): # tef 228 / com 234
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
website_menu=True
)
- self.env['event.event'].create([event_values])
+ self.env['event.event'].with_context(lang='en_US').create([event_values])
@users('event_user')
@warmup
@@ -212,13 +212,13 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=262): # tef 256 / com 255
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=271): # tef 256 / com 262
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
event_type_id=event_type.id,
)
- self.env['event.event'].create([event_values])
+ self.env['event.event'].with_context(lang='en_US').create([event_values])
@tagged('event_performance', 'registration_performance', 'post_install', '-at_install')
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index fbb0e2167bd..6883d1efd92 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -693,12 +693,19 @@ class Users(models.Model):
for name, key in name_to_key.items()
}
- # ensure the language is set and is compatible with the web client
- lang = context.get('lang') or (request and request.default_lang()) or DEFAULT_LANG
- if lang == 'ar_AR':
- context['lang'] = 'ar'
- if lang in babel.core.LOCALE_ALIASES:
- context['lang'] = babel.core.LOCALE_ALIASES[lang]
+ # ensure lang is set and available
+ # context > request > company > english > any lang installed
+ langs = [code for code, _ in self.env['res.lang'].get_installed()]
+ lang = context.get('lang')
+ if lang not in langs:
+ lang = request.best_lang if request else None
+ if lang not in langs:
+ lang = self.env.user.company_id.partner_id.lang
+ if lang not in langs:
+ lang = DEFAULT_LANG
+ if lang not in langs:
+ lang = langs[0] if langs else DEFAULT_LANG
+ context['lang'] = lang
# ensure uid is set
context['uid'] = self.env.uid
diff --git a/odoo/addons/base/tests/test_res_users.py b/odoo/addons/base/tests/test_res_users.py
index c52d67f616e..cd4a472de11 100644
--- a/odoo/addons/base/tests/test_res_users.py
+++ b/odoo/addons/base/tests/test_res_users.py
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from types import SimpleNamespace
+from unittest.mock import patch
from odoo.addons.base.models.res_users import is_selection_groups, get_selection_groups, name_selection_groups
from odoo.exceptions import UserError
-from odoo.tests.common import TransactionCase, Form, tagged
+from odoo.tests.common import Form, TransactionCase, new_test_user, tagged
from odoo.tools import mute_logger
@@ -188,6 +190,37 @@ class TestUsers(TransactionCase):
self.assertTrue(portal_partner_2.exists(), 'Should have kept the partner')
self.assertEqual(asked_deletion_2.state, 'fail', 'Should have marked the deletion as failed')
+ def test_context_get_lang(self):
+ self.env['res.lang'].with_context(active_test=False).search([
+ ('code', 'in', ['fr_FR', 'es_ES', 'de_DE', 'en_US'])
+ ]).write({'active': True})
+
+ user = new_test_user(self.env, 'jackoneill')
+ user = user.with_user(user)
+ user.lang = 'fr_FR'
+
+ company = user.company_id.partner_id.sudo()
+ company.lang = 'de_DE'
+
+ request = SimpleNamespace()
+ request.best_lang = 'es_ES'
+ request_patch = patch('odoo.addons.base.models.res_users.request', request)
+ self.addCleanup(request_patch.stop)
+ request_patch.start()
+
+ self.assertEqual(user.context_get()['lang'], 'fr_FR')
+ self.env.registry.clear_caches()
+ user.lang = False
+
+ self.assertEqual(user.context_get()['lang'], 'es_ES')
+ self.env.registry.clear_caches()
+ request_patch.stop()
+
+ self.assertEqual(user.context_get()['lang'], 'de_DE')
+ self.env.registry.clear_caches()
+ company.lang = False
+
+ self.assertEqual(user.context_get()['lang'], 'en_US')
@tagged('post_install', '-at_install')
class TestUsers2(TransactionCase):
diff --git a/odoo/http.py b/odoo/http.py
index 50845e36130..3feb5002e02 100644
--- a/odoo/http.py
+++ b/odoo/http.py
@@ -1372,6 +1372,22 @@ class Request:
_cr = cr
+ @lazy_property
+ def best_lang(self):
+ lang = self.httprequest.accept_languages.best
+ if not lang:
+ return None
+
+ try:
+ code, territory, _, _ = babel.core.parse_locale(lang, sep='-')
+ if territory:
+ lang = f'{code}_{territory}'
+ else:
+ lang = babel.core.LOCALE_ALIASES[code]
+ return lang
+ except (ValueError, KeyError):
+ return None
+
# =====================================================
# Helpers
# =====================================================
@@ -1434,19 +1450,7 @@ class Request:
:returns: Preferred language if specified or 'en_US'
:rtype: str
"""
- lang = self.httprequest.accept_languages.best
- if not lang:
- return DEFAULT_LANG
-
- try:
- code, territory, _, _ = babel.core.parse_locale(lang, sep='-')
- if territory:
- lang = f'{code}_{territory}'
- else:
- lang = babel.core.LOCALE_ALIASES[code]
- return lang
- except (ValueError, KeyError):
- return DEFAULT_LANG
+ return self.best_lang or DEFAULT_LANG
def get_http_params(self):
"""