From 54b45aec9ef0fc824af168b567bd0685bf519552 Mon Sep 17 00:00:00 2001 From: Julien Mougenot Date: Thu, 4 Nov 2021 15:50:07 +0000 Subject: [PATCH] [FIX] base_import_module: Keep manifest assets of dynamic modules The `ir_import_module` module is used by users to upload custom module data via a zip file. The zip file can contain xml data, xml views and static files. Before this commit, the "import module" feature of this module was broken since the imported module's manifest was lost after the import was performed. Now, 'ir.asset' records are created along with the attachments pointing to the imported static files. closes odoo/odoo#80120 X-original-commit: ec77577796e72707fbb2077f05067428cebc3c7f Signed-off-by: Julien Castiaux Signed-off-by: Julien Mougenot (jum) --- addons/base_import_module/models/ir_module.py | 46 +++++++++++++- addons/base_import_module/tests/__init__.py | 3 + .../tests/test_import_module.py | 59 ++++++++++++++++++ .../tests/test_module/__manifest__.py | 24 ------- .../tests/test_module/static/src/img/c64.png | Bin 24385 -> 0 bytes .../tests/test_module/static/src/js/test.js | 1 - .../tests/test_module/test.xml | 43 ------------- odoo/addons/base/models/ir_asset.py | 23 ++++--- 8 files changed, 121 insertions(+), 78 deletions(-) create mode 100644 addons/base_import_module/tests/__init__.py create mode 100644 addons/base_import_module/tests/test_import_module.py delete mode 100644 addons/base_import_module/tests/test_module/__manifest__.py delete mode 100644 addons/base_import_module/tests/test_module/static/src/img/c64.png delete mode 100644 addons/base_import_module/tests/test_module/static/src/js/test.js delete mode 100644 addons/base_import_module/tests/test_module/test.xml diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index f245ead654c..73c384451ae 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -106,6 +106,43 @@ class IrModule(models.Model): else: IrAttachment.create(values) + IrAsset = self.env['ir.asset'] + assets_vals = [] + + # Generate 'ir.asset' record values for each asset delared in the manifest + for bundle, commands in terp.get('assets', {}).items(): + for command in commands: + directive, target, path = IrAsset._process_command(command) + path = path if path.startswith('/') else '/' + path # Ensures a '/' at the start + assets_vals.append({ + 'name': f'{module}.{bundle}.{path}', + 'directive': directive, + 'target': target, + 'path': path, + 'bundle': bundle, + }) + + # Look for existing assets + existing_assets = IrAsset.search([('name', 'in', [vals['name'] for vals in assets_vals])]) + existing_assets = existing_assets.mapped(lambda r: (r.name, r)) + assets_to_create = [] + + # Update existing assets and generate the list of new assets values + for values in assets_vals: + if values['name'] in existing_assets: + existing_assets[values['name']].write(values) + else: + assets_to_create.append(values) + + # Create new assets and attach 'ir.model.data' records to them + created_assets = IrAsset.create(assets_to_create) + self.env['ir.model.data'].create([{ + 'name': f"{asset['bundle']}.{asset['path']}", + 'model': 'ir.asset', + 'module': module, + 'res_id': asset.id, + } for asset in created_assets]) + return True @api.model @@ -157,8 +194,15 @@ class IrModule(models.Model): modules_to_delete = self.filtered('imported') res = super().module_uninstall() if modules_to_delete: + deleted_modules_names = modules_to_delete.mapped('name') + assets_data = self.env['ir.model.data'].search([ + ('model', '=', 'ir.asset'), + ('module', 'in', deleted_modules_names), + ]) + assets = self.env['ir.asset'].search([('id', 'in', assets_data.mapped('res_id'))]) + assets.unlink() _logger.info("deleting imported modules upon uninstallation: %s", - ", ".join(modules_to_delete.mapped('name'))) + ", ".join(deleted_modules_names)) modules_to_delete.unlink() return res diff --git a/addons/base_import_module/tests/__init__.py b/addons/base_import_module/tests/__init__.py new file mode 100644 index 00000000000..50f27409655 --- /dev/null +++ b/addons/base_import_module/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import test_import_module diff --git a/addons/base_import_module/tests/test_import_module.py b/addons/base_import_module/tests/test_import_module.py new file mode 100644 index 00000000000..2b9398999d5 --- /dev/null +++ b/addons/base_import_module/tests/test_import_module.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import json +from io import BytesIO +from zipfile import ZipFile + +import odoo.tests + +@odoo.tests.tagged('post_install', '-at_install') +class TestImportModule(odoo.tests.TransactionCase): + + def test_import_and_uninstall_module(self): + bundle = 'web.assets_backend' + path = '/test_module/static/src/js/test.js' + manifest_content = json.dumps({ + 'name': 'Test Module', + 'description': 'Test', + 'assets': { + 'web.assets_backend': [ + 'test_module/static/src/js/test.js' + ] + }, + 'license': 'LGPL-3', + }) + + stream = BytesIO() + with ZipFile(stream, 'w') as archive: + archive.writestr('test_module/__manifest__.py', manifest_content) + archive.writestr('test_module/static/src/js/test.js', "console.log('AAA');") + + # Import test module + self.env['ir.module.module'].import_zipfile(stream) + + attachment = self.env['ir.attachment'].search([('url', '=', path)]) + self.assertEqual(attachment.name, 'test.js') + self.assertEqual(attachment.type, 'binary') + self.assertEqual(attachment.raw, b"console.log('AAA');") + + asset = self.env['ir.asset'].search([('name', '=', f'test_module.{bundle}.{path}')]) + self.assertEqual(asset.path, path) + self.assertEqual(asset.bundle, bundle) + self.assertEqual(asset.directive, 'append') + self.assertEqual(asset.target, False) + + asset_data = self.env['ir.model.data'].search([('model', '=', 'ir.asset'), ('res_id', '=', asset.id)]) + self.assertEqual(asset_data.module, 'test_module') + self.assertEqual(asset_data.name, f'{bundle}.{path}') + + # Uninstall test module + self.env['ir.module.module'].search([('name', '=', 'test_module')]).module_uninstall() + + attachment = self.env['ir.attachment'].search([('url', '=', path)]) + self.assertEqual(len(attachment), 1) + + asset = self.env['ir.asset'].search([('name', '=', f'test_module.{bundle}.{path}')]) + self.assertEqual(len(asset), 0) + + asset_data = self.env['ir.model.data'].search([('model', '=', 'ir.asset'), ('res_id', '=', asset.id)]) + self.assertEqual(len(asset_data), 0) diff --git a/addons/base_import_module/tests/test_module/__manifest__.py b/addons/base_import_module/tests/test_module/__manifest__.py deleted file mode 100644 index 44cf4586abd..00000000000 --- a/addons/base_import_module/tests/test_module/__manifest__.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -{ - 'name': 'Test Module', - 'category': 'Website/Website', - 'summary': 'Custom', - 'version': '1.0', - 'description': """ - Test - """, - 'depends': ['website'], - 'data': [ - 'test.xml', - ], - 'assets': { - 'web.assets_backend': [ - 'test_module/static/src/js/test.js' - ] - }, - 'installable': True, - 'application': True, - 'license': 'LGPL-3', -} diff --git a/addons/base_import_module/tests/test_module/static/src/img/c64.png b/addons/base_import_module/tests/test_module/static/src/img/c64.png deleted file mode 100644 index 3e8183f934fed7d4fa6548604ece0c8e7f6d3aba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24385 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 - - - - The base company is noupdate=1 - - - - Hagrid - My Company Tagline - - - - - - - - - - diff --git a/odoo/addons/base/models/ir_asset.py b/odoo/addons/base/models/ir_asset.py index 72289224942..ab74e233ab4 100644 --- a/odoo/addons/base/models/ir_asset.py +++ b/odoo/addons/base/models/ir_asset.py @@ -10,7 +10,6 @@ from werkzeug import urls import odoo from odoo.tools import misc from odoo import tools -from odoo.addons import __path__ as ADDONS_PATH from odoo import api, fields, http, models from odoo.http import root @@ -215,14 +214,7 @@ class IrAsset(models.Model): continue manifest_assets = manifest.get('assets', {}) for command in manifest_assets.get(bundle, []): - if isinstance(command, str): - # Default directive: append - directive, target, path_def = APPEND_DIRECTIVE, None, command - elif command[0] in DIRECTIVES_WITH_TARGET: - directive, target, path_def = command - else: - directive, path_def = command - target = None + directive, target, path_def = self._process_command(command) process_path(directive, target, path_def) # 3. Process the rest of 'ir.asset' records @@ -388,6 +380,19 @@ class IrAsset(models.Model): if not extensions or path.split('.')[-1] in extensions ] + def _process_command(self, command): + """Parses a given command to return its directive, target and path definition.""" + if isinstance(command, str): + # Default directive: append + directive, target, path_def = APPEND_DIRECTIVE, None, command + elif command[0] in DIRECTIVES_WITH_TARGET: + directive, target, path_def = command + else: + directive, path_def = command + target = None + return directive, target, path_def + + class AssetPaths: """ A list of asset paths (path, addon, bundle) with efficient operations. """ def __init__(self):