diff --git a/base_manifest_extension/README.rst b/base_manifest_extension/README.rst new file mode 100644 index 000000000..0bda0b233 --- /dev/null +++ b/base_manifest_extension/README.rst @@ -0,0 +1,65 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +========================= +Extra options in manifest +========================= + +This is a technical module to allow developers to make use of a couple of new keys in module manifests. + +The following new keys are available currently: + +depends_if_installed + Your module will depend on modules listed here, but only if those modules are already installed. This is useful if your module is supposed to override behavior of a third module if present, but it's not a hard dependency of your module. Think of auth_* and their interactions. + +Usage +===== + +* depend on this module and use the keys described above + +Roadmap +======= + +* ``breaks`` to mark some modules as being incompatible. This also could be versioned (``'breaks': ['my_module<<0.4.2']``) +* ``demo_if_installed``, ``data_if_installed``, ``qweb_if_installed`` being dicts with module names as keys and a list of files as values in order to pull some data files in case some other module is installed +* ``rdepends_if_installed`` to make another installed module depend on the current module +* ``_if_module`` on models to load certain models only if the appropriate module is installed + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Holger Brunn + +Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list `_ or the `appropriate specialized mailinglist `_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/base_manifest_extension/__init__.py b/base_manifest_extension/__init__.py new file mode 100644 index 000000000..85185497d --- /dev/null +++ b/base_manifest_extension/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from .hooks import post_load_hook diff --git a/base_manifest_extension/__openerp__.py b/base_manifest_extension/__openerp__.py new file mode 100644 index 000000000..8f89a484f --- /dev/null +++ b/base_manifest_extension/__openerp__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "More options in manifest", + "version": "8.0.1.0.0", + "author": "Therp BV,Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Hidden/Dependency", + "summary": "Adds some useful keys to manifest files", + "depends": [ + 'base', + ], + "post_load": 'post_load_hook', +} diff --git a/base_manifest_extension/hooks.py b/base_manifest_extension/hooks.py new file mode 100644 index 000000000..fd6f857e7 --- /dev/null +++ b/base_manifest_extension/hooks.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import inspect +from openerp.sql_db import Cursor +from openerp.modules import module +from openerp.modules.graph import Graph +original = module.load_information_from_description_file + + +def load_information_from_description_file(module, mod_path=None): + result = original(module, mod_path=mod_path) + # add the keys you want to react on here + if result.get('depends_if_installed'): + cr = _get_cr() + if cr: + _handle_depends_if_installed(cr, result) + + return result + + +def _handle_depends_if_installed(cr, manifest): + if not manifest.get('depends_if_installed'): + return + cr.execute( + 'select name from ir_module_module ' + 'where state in %s and name in %s', + ( + tuple(['installed', 'to install', 'to upgrade']), + tuple(manifest['depends_if_installed']), + ), + ) + manifest.pop('depends_if_installed') + depends = manifest.setdefault('depends', []) + depends.extend(module for module, in cr.fetchall()) + + +def _get_cr(): + cr = None + for frame, filename, lineno, funcname, line, index in inspect.stack(): + # walk up the stack until we've found a cursor + if 'cr' in frame.f_locals and isinstance(frame.f_locals['cr'], Cursor): + cr = frame.f_locals['cr'] + break + return cr + + +def _get_graph(): + graph = None + for frame, filename, lineno, funcname, line, index in inspect.stack(): + # walk up the stack until we've found a graph + if 'graph' in frame.f_locals and isinstance( + frame.f_locals['graph'], Graph + ): + graph = frame.f_locals['graph'] + break + return graph + + +def post_load_hook(): + cr = _get_cr() + if not cr: + return + cr.execute( + 'select id from ir_module_module where name=%s and state in %s', + ( + 'base_manifest_extension', + tuple(['installed', 'to install', 'to upgrade']), + ), + ) + # do nothing if we're not installed + if not cr.rowcount: + return + + module.load_information_from_description_file =\ + load_information_from_description_file + + # here stuff can become tricky: On the python level, modules + # are not loaded in dependency order. This means that there might + # be modules loaded depending on us before we could patch the function + # above. So we reload the module graph for all modules coming after us + + graph = _get_graph() + if not graph: + return + + this = graph['base_manifest_extension'] + to_reload = [] + for node in graph.itervalues(): + if node.depth > this.depth: + to_reload.append(node.name) + for module_name in to_reload: + del graph[module_name] + graph.add_modules(cr, to_reload) diff --git a/base_manifest_extension/static/description/icon.png b/base_manifest_extension/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/base_manifest_extension/static/description/icon.png differ diff --git a/base_manifest_extension/tests/__init__.py b/base_manifest_extension/tests/__init__.py new file mode 100644 index 000000000..535989849 --- /dev/null +++ b/base_manifest_extension/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_base_manifest_extension diff --git a/base_manifest_extension/tests/test_base_manifest_extension.py b/base_manifest_extension/tests/test_base_manifest_extension.py new file mode 100644 index 000000000..959839b90 --- /dev/null +++ b/base_manifest_extension/tests/test_base_manifest_extension.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# © 2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import os +import tempfile +from openerp.tests.common import TransactionCase +from openerp.modules.module import load_information_from_description_file,\ + get_module_path, MANIFEST + + +class TestBaseManifestExtension(TransactionCase): + def test_base_manifest_extension(self): + # write a test manifest + module_path = tempfile.mkdtemp(dir=os.path.join( + get_module_path('base_manifest_extension'), 'static' + )) + with open(os.path.join(module_path, MANIFEST), 'w') as manifest: + manifest.write(repr({ + 'depends_if_installed': [ + 'base_manifest_extension', + 'not installed', + ], + })) + # parse it + parsed = load_information_from_description_file( + # this name won't really be used, but avoids a warning + 'base', mod_path=module_path, + ) + self.assertIn('base_manifest_extension', parsed['depends']) + self.assertNotIn('not installed', parsed['depends']) + self.assertNotIn('depends_if_installed', parsed)