diff --git a/setup/web_fix_modules_load/odoo/addons/web_fix_modules_load b/setup/web_fix_modules_load/odoo/addons/web_fix_modules_load new file mode 120000 index 000000000..07934893c --- /dev/null +++ b/setup/web_fix_modules_load/odoo/addons/web_fix_modules_load @@ -0,0 +1 @@ +../../../../web_fix_modules_load \ No newline at end of file diff --git a/setup/web_fix_modules_load/setup.py b/setup/web_fix_modules_load/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_fix_modules_load/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_fix_modules_load/README.rst b/web_fix_modules_load/README.rst new file mode 100644 index 000000000..771ac7742 --- /dev/null +++ b/web_fix_modules_load/README.rst @@ -0,0 +1 @@ +bot, please! \ No newline at end of file diff --git a/web_fix_modules_load/__init__.py b/web_fix_modules_load/__init__.py new file mode 100644 index 000000000..91c5580fe --- /dev/null +++ b/web_fix_modules_load/__init__.py @@ -0,0 +1,2 @@ +from . import controllers +from . import models diff --git a/web_fix_modules_load/__manifest__.py b/web_fix_modules_load/__manifest__.py new file mode 100644 index 000000000..3af1b631c --- /dev/null +++ b/web_fix_modules_load/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2022 Camptocamp SA +# @author Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Fix translation loading", + "summary": "Fix translations loading from frontend with many modules", + "version": "14.0.1.0.0", + "category": "Hidden", + "license": "AGPL-3", + "development_status": "Alpha", + "website": "https://github.com/OCA/web", + "author": "Camptocamp,Odoo Community Association (OCA)", + "maintainers": ["simahawk"], + "depends": [ + "web", + ], + "data": ["templates/assets.xml"], + "installable": True, +} diff --git a/web_fix_modules_load/controllers/__init__.py b/web_fix_modules_load/controllers/__init__.py new file mode 100644 index 000000000..12a7e529b --- /dev/null +++ b/web_fix_modules_load/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/web_fix_modules_load/controllers/main.py b/web_fix_modules_load/controllers/main.py new file mode 100644 index 000000000..453194e96 --- /dev/null +++ b/web_fix_modules_load/controllers/main.py @@ -0,0 +1,48 @@ +# Copyright 2022 Camptocamp SA +# @author Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import http + +from odoo.addons.web.controllers.main import WebClient + + +class WebClientPatched(WebClient): + """Handle conversions of modules' ids to their names.""" + + def _get_mod_names(self, mods): + """Retrieve module names from their IDs in any form.""" + mod_names = mods + model = http.request.env["ir.module.module"].sudo() + if isinstance(mods, str): + mods = [int(x.strip()) for x in mods.split(",") if x.strip().isdigit()] + if mods and isinstance(mods[0], int): + mod_names = model.browse(mods).mapped("name") + return mod_names + + @http.route() + def qweb(self, unique, mods=None, db=None): + # Here `mods` comes as 1,2,3,4 string + mods = self._get_mod_names(mods) + return super().qweb(unique, mods=mods, db=db) + + @http.route() + def bootstrap_translations(self, mods): + mods = self._get_mod_names(mods) + return super().bootstrap_translations(mods) + + @http.route() + def csslist(self, mods=None): + mods = self._get_mod_names(mods) + return super().csslist(mods=mods) + + @http.route() + def jslist(self, mods=None): + mods = self._get_mod_names(mods) + return super().jslist(mods=mods) + + @http.route() + def translations(self, unique, mods=None, lang=None): + mods = self._get_mod_names(mods) + mods = ",".join(mods) + return super().translations(unique, mods, lang) diff --git a/web_fix_modules_load/models/__init__.py b/web_fix_modules_load/models/__init__.py new file mode 100644 index 000000000..da41a9e49 --- /dev/null +++ b/web_fix_modules_load/models/__init__.py @@ -0,0 +1,2 @@ +from . import ir_module +from . import ir_translation diff --git a/web_fix_modules_load/models/ir_module.py b/web_fix_modules_load/models/ir_module.py new file mode 100644 index 000000000..87cb02c27 --- /dev/null +++ b/web_fix_modules_load/models/ir_module.py @@ -0,0 +1,20 @@ +# Copyright 2022 Camptocamp SA +# @author Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json + +from odoo import models + +from odoo.addons.web.controllers.main import module_boot + + +class IrModule(models.Model): + _inherit = "ir.module.module" + + def _session_modules_info(self): + """Load modules info and return their mapping.""" + module_names = module_boot(self.env.cr.dbname) + modules = self.sudo().search([("name", "in", module_names)]) + data = {mod.name: {"id": mod.id} for mod in modules} + return json.dumps(data) diff --git a/web_fix_modules_load/models/ir_translation.py b/web_fix_modules_load/models/ir_translation.py new file mode 100644 index 000000000..33a76d75e --- /dev/null +++ b/web_fix_modules_load/models/ir_translation.py @@ -0,0 +1,17 @@ +# Copyright 2022 Camptocamp SA +# @author Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo import models + + +class IrTranslation(models.Model): + _inherit = "ir.translation" + + def get_translations_for_webclient(self, mods, lang): + # Intercept call to load translations from modules' ids instead of names. + if mods and isinstance(mods[0], int): + model = self.env["ir.module.module"].sudo() + mods = model.browse(mods).mapped("name") + return super().get_translations_for_webclient(mods, lang) diff --git a/web_fix_modules_load/readme/CONTRIBUTORS.rst b/web_fix_modules_load/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..f1c71bce1 --- /dev/null +++ b/web_fix_modules_load/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Simone Orsi diff --git a/web_fix_modules_load/readme/DESCRIPTION.rst b/web_fix_modules_load/readme/DESCRIPTION.rst new file mode 100644 index 000000000..472c88083 --- /dev/null +++ b/web_fix_modules_load/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +Odoo loads translations and module info using their names. +When you have a lot of modules installed (eg: 500+) +this can lead to very big GET requests (more than 12k) which, in most of the cases, +will be blocked by the web server (eg: nginx) because they are too big. + +This module tries to fix this by using modules' ids instead of names +reducing dramatically the size of such requests. diff --git a/web_fix_modules_load/static/src/js/session.js b/web_fix_modules_load/static/src/js/session.js new file mode 100644 index 000000000..470147490 --- /dev/null +++ b/web_fix_modules_load/static/src/js/session.js @@ -0,0 +1,127 @@ +odoo.define("web_load_translations_fix.Session", function (require) { + "use strict"; + + var Session = require("web.Session"); + var core = require("web.core"); + var _t = core._t; + + /** + * Override session manager to change how modules' are loaded: use ids instead of names. + * + */ + Session.include({ + _modules_info: function () { + return odoo._modules_info; + }, + _module_ids: function (names) { + const mod_names = names ? names : this.module_list; + const info = this._modules_info(); + const ids = []; + _.each(mod_names, function (name) { + if (info[name]) { + ids.push(info[name].id); + } + }); + console.debug("Session: load module names", mod_names); + return ids; + }, + /** + * Full override due to no available hook. + * The whole code is taken as-is from Odoo core (comments included). + * Only the call to `load_translations` has been modified to use `_module_ids`. + * @returns: Promise + */ + load_translations: function () { + var lang = this.user_context.lang; + /* We need to get the website lang at this level. + The only way is to get it is to take the HTML tag lang + Without it, we will always send undefined if there is no lang + in the user_context. */ + var html = document.documentElement, + htmlLang = html.getAttribute("lang"); + if (!this.user_context.lang && htmlLang) { + lang = htmlLang.replace("-", "_"); + } + return _t.database.load_translations( + this, + this._module_ids(), + lang, + this.translationURL + ); + }, + /** + * Full override due to no available hook. + * The whole code is taken as-is from Odoo core (comments included). + * Only the call to `load_qweb` and `bootstrap_translations` have been modified to use `_module_ids`. + * @returns: Promise + */ + session_init: function () { + var self = this; + var prom = this.session_reload(); + + if (this.is_frontend) { + return prom.then(function () { + return self.load_translations(); + }); + } + + return prom.then(function () { + var promise = self.load_qweb(self._module_ids().join(",")); + if (self.session_is_valid()) { + return promise.then(function () { + return self.load_modules(); + }); + } + return Promise.all([ + promise, + self + .rpc("/web/webclient/bootstrap_translations", { + mods: self._module_ids(), + }) + .then(function (trans) { + _t.database.set_bundle(trans); + }), + ]); + }); + }, + /** + * Full override due to no available hook. + * The whole code is taken as-is from Odoo core (comments included). + * Only the call to `csslist` and `jslist` have been modified to use `_module_ids`. + * @returns: Promise + */ + load_modules: function () { + var self = this; + var modules = odoo._modules; + var all_modules = _.uniq(self.module_list.concat(modules)); + var to_load = _.difference(modules, self.module_list).join(","); + this.module_list = all_modules; + var loaded = Promise.resolve(self.load_translations()); + var locale = "/web/webclient/locale/" + self.user_context.lang || "en_US"; + var file_list = [locale]; + if (to_load.length) { + loaded = Promise.all([ + loaded, + self + .rpc("/web/webclient/csslist", { + mods: self._module_ids(to_load), + }) + .then(self.load_css.bind(self)), + self.load_qweb(to_load), + self + .rpc("/web/webclient/jslist", {mods: self._module_ids(to_load)}) + .then(function (files) { + file_list = file_list.concat(files); + }), + ]); + } + return loaded + .then(function () { + return self.load_js(file_list); + }) + .then(function () { + self._configureLocale(); + }); + }, + }); +}); diff --git a/web_fix_modules_load/templates/assets.xml b/web_fix_modules_load/templates/assets.xml new file mode 100644 index 000000000..2b4cf5204 --- /dev/null +++ b/web_fix_modules_load/templates/assets.xml @@ -0,0 +1,20 @@ + + + + + +