diff --git a/addons/account/models/ir_module.py b/addons/account/models/ir_module.py index 8f9330c77e6..21089d61730 100644 --- a/addons/account/models/ir_module.py +++ b/addons/account/models/ir_module.py @@ -2,11 +2,7 @@ from importlib import import_module from inspect import getmembers, ismodule, isclass, isfunction from odoo import models, fields - - -def get_flag(country_code): - # get the sequence of 2 regional indicator emoji forming the flag linked to the country code - return "".join(chr(int(f"1f1{ord(c)+165:02x}", base=16)) for c in country_code) +from odoo.tools import get_flag def templ(env, code, name=None, country='', **kwargs): diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py index a05f4d50406..0afa6c0823c 100644 --- a/odoo/addons/base/models/ir_module.py +++ b/odoo/addons/base/models/ir_module.py @@ -30,7 +30,7 @@ from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG from odoo.exceptions import AccessDenied, UserError from odoo.osv import expression from odoo.tools.parse_version import parse_version -from odoo.tools.misc import topological_sort +from odoo.tools.misc import topological_sort, get_flag from odoo.tools.translate import TranslationImporter from odoo.http import request from odoo.modules import get_module_path, get_module_resource @@ -239,6 +239,9 @@ class Module(models.Model): if path: with tools.file_open(path, 'rb') as image_file: module.icon_image = base64.b64encode(image_file.read()) + countries = self.get_module_info(module.name).get('countries', []) + country_code = len(countries) == 1 and countries[0] + module.icon_flag = get_flag(country_code.upper()) if country_code else '' name = fields.Char('Technical Name', readonly=True, required=True) category_id = fields.Many2one('ir.module.category', string='Category', readonly=True, index=True) @@ -289,6 +292,7 @@ class Module(models.Model): application = fields.Boolean('Application', readonly=True) icon = fields.Char('Icon URL') icon_image = fields.Binary(string='Icon', compute='_get_icon_image') + icon_flag = fields.Char(string='Flag', compute='_get_icon_image') to_buy = fields.Boolean('Odoo Enterprise Module', default=False) has_iap = fields.Boolean(compute='_compute_has_iap') diff --git a/odoo/addons/base/static/src/css/modules.css b/odoo/addons/base/static/src/css/modules.css index 755299a54b2..d26a9028c06 100644 --- a/odoo/addons/base/static/src/css/modules.css +++ b/odoo/addons/base/static/src/css/modules.css @@ -6,6 +6,14 @@ float: left; } +.oe_module_flag { + position: absolute; + left: 12px; + top: calc(50% - 35px); + font: 27px icon; + text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5); +} + .oe_module_desc { font-size: 13px; padding-left: 10px; diff --git a/odoo/addons/base/tests/test_module.py b/odoo/addons/base/tests/test_module.py index ca57f0aa630..512d9888ffd 100644 --- a/odoo/addons/base/tests/test_module.py +++ b/odoo/addons/base/tests/test_module.py @@ -41,6 +41,7 @@ class TestModuleManifest(BaseCase): 'auto_install': False, 'bootstrap': False, 'category': 'Uncategorized', + 'countries': [], 'data': [], 'demo': [], 'demo_xml': [], diff --git a/odoo/addons/base/views/ir_module_views.xml b/odoo/addons/base/views/ir_module_views.xml index d02107d0c09..bae939a3cad 100644 --- a/odoo/addons/base/views/ir_module_views.xml +++ b/odoo/addons/base/views/ir_module_views.xml @@ -155,6 +155,7 @@ + @@ -171,7 +172,10 @@
- Icon +
+ Icon + +

  diff --git a/odoo/addons/test_lint/tests/test_manifests.py b/odoo/addons/test_lint/tests/test_manifests.py index 324fc204d77..d5fd176e518 100644 --- a/odoo/addons/test_lint/tests/test_manifests.py +++ b/odoo/addons/test_lint/tests/test_manifests.py @@ -53,6 +53,11 @@ class ManifestLinter(BaseCase): # todo installable ? ] + if 'countries' in manifest_data and 'l10n' not in module: + _logger.warning( + "Module %s specific to certain countries %s should contain `l10n` in their name.", + module, manifest_data['countries']) + for key in manifest_data: value = manifest_data[key] if key in _DEFAULT_MANIFEST: @@ -75,6 +80,9 @@ class ManifestLinter(BaseCase): _logger.warning( "Wrong type for manifest value %s in module %s, expected bool or list", key, module) + else: + if key == 'countries': + self._test_manifest_countries_value(module, value) elif key == 'icon': self._test_manifest_icon_value(module, value) @@ -103,3 +111,11 @@ class ManifestLinter(BaseCase): "Icon value specified in manifest of module %s wasn't found in given path." " Please specify a correct value or remove this key from the manifest.", module) + + def _test_manifest_countries_value(self, module, values): + for value in values: + if value and len(value) != 2: + _logger.warning( + "Country value %s specified for the icon in manifest of module %s doesn't look like a country code" + "Please specify a correct value or remove this key from the manifest.", + value, module) diff --git a/odoo/modules/module.py b/odoo/modules/module.py index ad2d19d732e..b56b5485a09 100644 --- a/odoo/modules/module.py +++ b/odoo/modules/module.py @@ -30,6 +30,7 @@ _DEFAULT_MANIFEST = { 'author': 'Odoo S.A.', 'auto_install': False, 'category': 'Uncategorized', + 'countries': [], 'data': [], 'demo': [], 'demo_xml': [], diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index ac401846c5c..08ab9f9b023 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -1729,3 +1729,10 @@ def has_list_types(values, types): isinstance(values, (list, tuple)) and len(values) == len(types) and all(isinstance(item, type_) for item, type_ in zip(values, types)) ) + +def get_flag(country_code: str) -> str: + """Get the emoji representing the flag linked to the country code. + + This emoji is composed of the two regional indicator emoji of the country code. + """ + return "".join(chr(int(f"1f1{ord(c)+165:02x}", base=16)) for c in country_code)