vanilla 19.0

This commit is contained in:
Ernad Husremovic 2025-10-08 10:49:46 +02:00
parent 991d2234ca
commit d1963a3c3a
3066 changed files with 1651266 additions and 922560 deletions

View file

@ -1,66 +1,21 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import logging
import warnings
import werkzeug
import werkzeug.exceptions
import werkzeug.utils
import werkzeug.wrappers
import werkzeug.wsgi
import odoo
import odoo.modules.registry
import odoo.tools
from odoo import http
from odoo.modules import get_manifest, get_resource_path
from odoo.modules import Manifest
from odoo.http import request
from odoo.tools import lazy
from odoo.tools.misc import file_open
from odoo.tools.misc import file_path
from .utils import _local_web_translations
_logger = logging.getLogger(__name__)
@lazy
def CONTENT_MAXAGE():
warnings.warn("CONTENT_MAXAGE is a deprecated alias to odoo.http.STATIC_CACHE_LONG", DeprecationWarning)
return http.STATIC_CACHE_LONG
MOMENTJS_LANG_CODES_MAP = {
"sr_RS": "sr_cyrl",
"sr@latin": "sr"
}
class WebClient(http.Controller):
@http.route('/web/webclient/locale/<string:lang>', type='http', auth="none")
def load_locale(self, lang):
lang = MOMENTJS_LANG_CODES_MAP.get(lang, lang)
magic_file_finding = [lang.replace("_", '-').lower(), lang.split('_')[0]]
for code in magic_file_finding:
try:
return http.Response(
werkzeug.wsgi.wrap_file(
request.httprequest.environ,
file_open(f'web/static/lib/moment/locale/{code}.js', 'rb')
),
content_type='application/javascript; charset=utf-8',
headers=[('Cache-Control', f'max-age={http.STATIC_CACHE}')],
direct_passthrough=True,
)
except IOError:
_logger.debug("No moment locale for code %s", code)
return request.make_response("", headers=[
('Content-Type', 'application/javascript'),
('Cache-Control', f'max-age={http.STATIC_CACHE}'),
])
@http.route('/web/webclient/bootstrap_translations', type='json', auth="none")
@http.route('/web/webclient/bootstrap_translations', type='jsonrpc', auth="none")
def bootstrap_translations(self, mods=None):
""" Load local translations from *.po files, as a temporary solution
until we have established a valid session. This is meant only
@ -72,15 +27,15 @@ class WebClient(http.Controller):
lang = request.env.context['lang'].partition('_')[0]
if mods is None:
mods = odoo.conf.server_wide_modules or []
mods = odoo.tools.config['server_wide_modules']
if request.db:
mods = request.env.registry._init_modules.union(mods)
translations_per_module = {}
for addon_name in mods:
manifest = get_manifest(addon_name)
manifest = Manifest.for_addon(addon_name)
if manifest and manifest['bootstrap']:
f_name = get_resource_path(addon_name, 'i18n', f'{lang}.po')
f_name = file_path(f'{addon_name}/i18n/{lang}.po')
if not f_name:
continue
translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
@ -88,67 +43,73 @@ class WebClient(http.Controller):
return {"modules": translations_per_module,
"lang_parameters": None}
@http.route('/web/webclient/translations/<string:unique>', type='http', auth="public", cors="*")
def translations(self, unique, mods=None, lang=None):
@http.route('/web/webclient/translations', type='http', auth='public', cors='*', readonly=True)
def translations(self, hash=None, mods=None, lang=None):
"""
Load the translations for the specified language and modules
:param unique: this parameters is not used, but mandatory: it is used by the HTTP stack to make a unique request
:param hash: translations hash, which identifies a version of translations. This method only returns translations if their hash differs from the received one
:param mods: the modules, a comma separated list
:param lang: the language of the user
:return:
"""
if mods:
mods = mods.split(',')
elif mods is None:
mods = list(request.env.registry._init_modules) + (odoo.conf.server_wide_modules or [])
else:
mods = request.env.registry._init_modules.union(odoo.tools.config['server_wide_modules'])
translations_per_module, lang_params = request.env["ir.http"].get_translations_for_webclient(mods, lang)
if lang and lang not in {code for code, _ in request.env['res.lang'].sudo().get_installed()}:
lang = None
body = json.dumps({
'lang': lang_params and lang_params["code"],
'lang_parameters': lang_params,
'modules': translations_per_module,
'multi_lang': len(request.env['res.lang'].sudo().get_installed()) > 1,
})
response = request.make_response(body, [
# this method must specify a content-type application/json instead of using the default text/html set because
# the type of the route is set to HTTP, but the rpc is made with a get and expects JSON
('Content-Type', 'application/json'),
current_hash = request.env["ir.http"].with_context(cache_translation_data=True)._get_web_translations_hash(mods, lang)
body = {
'lang': lang,
'hash': current_hash,
}
if current_hash != hash:
if 'translation_data' in request.env.cr.cache:
# ormcache of _get_web_translations_hash was cold and fill the translation_data cache
body.update(request.env.cr.cache.pop('translation_data'))
else:
# ormcache of _get_web_translations_hash was hot
translations_per_module, lang_params = request.env["ir.http"]._get_translations_for_webclient(mods, lang)
body.update({
'lang_parameters': lang_params,
'modules': translations_per_module,
'multi_lang': len(request.env['res.lang'].sudo().get_installed()) > 1,
})
# The type of the route is set to HTTP, but the rpc is made with a get and expects JSON
return request.make_json_response(body, [
('Cache-Control', f'public, max-age={http.STATIC_CACHE_LONG}'),
])
return response
@http.route('/web/webclient/version_info', type='json', auth="none")
@http.route('/web/webclient/version_info', type='jsonrpc', auth="none")
def version_info(self):
return odoo.service.common.exp_version()
@http.route('/web/tests', type='http', auth="user")
@http.route('/web/tests', type='http', auth='user', readonly=True)
def unit_tests_suite(self, mod=None, **kwargs):
return request.render('web.unit_tests_suite', {'session_info': {'view_info': request.env['ir.ui.view'].get_view_info()}})
@http.route('/web/tests/legacy', type='http', auth='user', readonly=True)
def test_suite(self, mod=None, **kwargs):
return request.render('web.qunit_suite')
return request.render('web.qunit_suite', {'session_info': {'view_info': request.env['ir.ui.view'].get_view_info()}})
@http.route('/web/tests/mobile', type='http', auth="none")
def test_mobile_suite(self, mod=None, **kwargs):
return request.render('web.qunit_mobile_suite')
@http.route('/web/benchmarks', type='http', auth="none")
def benchmarks(self, mod=None, **kwargs):
return request.render('web.benchmark_suite')
@http.route('/web/bundle/<string:bundle_name>', auth="public", methods=["GET"])
@http.route('/web/bundle/<string:bundle_name>', auth='public', methods=['GET'], readonly=True)
def bundle(self, bundle_name, **bundle_params):
"""
Request the definition of a bundle, including its javascript and css bundled assets
"""
if 'lang' in bundle_params:
request.update_context(lang=bundle_params['lang'])
request.update_context(lang=request.env['res.lang']._get_code(bundle_params['lang']))
debug = bundle_params.get('debug', request.session.debug)
files = request.env["ir.qweb"]._get_asset_nodes(bundle_name, debug=debug, js=True, css=True)
data = [{
"type": tag,
"src": attrs.get("src") or attrs.get("data-src") or attrs.get('href'),
"content": content,
} for tag, attrs, content in files]
} for tag, attrs in files]
return request.make_json_response(data)