Currently, auto_install is triggered when all dependencies get installed, but there are cases where one would want such trigger on only a subset thereof. e.g. we want `website_sale_dashboard` to auto-install when `website_sale` is installed. Currently, it requires `web_dashboard` to also be auto-installed otherwise `website_sale_dashboard` would "wait" for both dependencies to be explicitly installed before the auto-install triggers. That's despite `web_dashboard` not being very useful on its own. More generally this is an issue with technical modules which need to be marked as auto_install so as not to block e.g. bridge modules from automatically installing. This change allows setting `auto_install` to a subset of `depends`: * if auto_install is set to `False`, the module does not get automatically installed (no change in semantics) * if auto_install is set to `True`, the module gets automatically installed if and only if all its dependencies are installed (also no change in semantics) * if auto_install is set to a list of dependencies, the module will be installed when all *these* dependencies are installed, other dependencies (excluded from auto_install) will be installed alongside as a consequence * auto_install can be set to an empty list, in this case the module will always be automatically installed regardless of its dependencies (and will force their installation). So after this change, `web_dashboard`'s auto_install can be set to `False` (such that it's not installed if no module defining dashboards is installed) and `website_sale_dashboard`'s manifest can be edited to: 'auto_install': ['website_sale'] possibilities: # no automatic installation 'depends': ['a', 'b'], 'auto_install': False # automatic installation if both a and b are installed 'depends': ['a', 'b'], 'auto_install': True # automatic installation if both a and b are installed (explicit) 'depends': ['a', 'b'], 'auto_install': ['a', 'b'] # automatic installation if b is installed, a will get forcefully # installed if it isn't yet 'depends': ['a', 'b'], 'auto_install': ['b'] # always automatically installed, will cause the installation of # its dependencies even if they're not marked explicitly 'depends': ['a', 'b'], 'auto_install': [] Task 1851328 closes odoo/odoo#29431 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
128 lines
4.9 KiB
Python
128 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import odoo.modules
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
def is_initialized(cr):
|
|
""" Check if a database has been initialized for the ORM.
|
|
|
|
The database can be initialized with the 'initialize' function below.
|
|
|
|
"""
|
|
return odoo.tools.table_exists(cr, 'ir_module_module')
|
|
|
|
def initialize(cr):
|
|
""" Initialize a database with for the ORM.
|
|
|
|
This executes base/data/base_data.sql, creates the ir_module_categories
|
|
(taken from each module descriptor file), and creates the ir_module_module
|
|
and ir_model_data entries.
|
|
|
|
"""
|
|
f = odoo.modules.get_module_resource('base', 'data', 'base_data.sql')
|
|
if not f:
|
|
m = "File not found: 'base.sql' (provided by module 'base')."
|
|
_logger.critical(m)
|
|
raise IOError(m)
|
|
|
|
with odoo.tools.misc.file_open(f) as base_sql_file:
|
|
cr.execute(base_sql_file.read())
|
|
|
|
for i in odoo.modules.get_modules():
|
|
mod_path = odoo.modules.get_module_path(i)
|
|
if not mod_path:
|
|
continue
|
|
|
|
# This will raise an exception if no/unreadable descriptor file.
|
|
info = odoo.modules.load_information_from_description_file(i)
|
|
|
|
if not info:
|
|
continue
|
|
categories = info['category'].split('/')
|
|
category_id = create_categories(cr, categories)
|
|
|
|
if info['installable']:
|
|
state = 'uninstalled'
|
|
else:
|
|
state = 'uninstallable'
|
|
|
|
cr.execute('INSERT INTO ir_module_module \
|
|
(author, website, name, shortdesc, description, \
|
|
category_id, auto_install, state, web, license, application, icon, sequence, summary) \
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', (
|
|
info['author'],
|
|
info['website'], i, info['name'],
|
|
info['description'], category_id,
|
|
info['auto_install'] is not False, state,
|
|
info['web'],
|
|
info['license'],
|
|
info['application'], info['icon'],
|
|
info['sequence'], info['summary']))
|
|
id = cr.fetchone()[0]
|
|
cr.execute('INSERT INTO ir_model_data \
|
|
(name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', (
|
|
'module_'+i, 'ir.module.module', 'base', id, True))
|
|
dependencies = info['depends']
|
|
for d in dependencies:
|
|
cr.execute(
|
|
'INSERT INTO ir_module_module_dependency (module_id, name, auto_install_required)'
|
|
' VALUES (%s, %s, %s)',
|
|
(id, d, d in (info['auto_install'] or ()))
|
|
)
|
|
|
|
# Install recursively all auto-installing modules
|
|
while True:
|
|
cr.execute("""SELECT m.name FROM ir_module_module m WHERE m.auto_install AND state != 'to install'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM ir_module_module_dependency d JOIN ir_module_module mdep ON (d.name = mdep.name)
|
|
WHERE d.module_id = m.id AND mdep.state != 'to install'
|
|
)""")
|
|
to_auto_install = [x[0] for x in cr.fetchall()]
|
|
if not to_auto_install: break
|
|
cr.execute("""UPDATE ir_module_module SET state='to install' WHERE name in %s""", (tuple(to_auto_install),))
|
|
|
|
def create_categories(cr, categories):
|
|
""" Create the ir_module_category entries for some categories.
|
|
|
|
categories is a list of strings forming a single category with its
|
|
parent categories, like ['Grand Parent', 'Parent', 'Child'].
|
|
|
|
Return the database id of the (last) category.
|
|
|
|
"""
|
|
p_id = None
|
|
category = []
|
|
while categories:
|
|
category.append(categories[0])
|
|
xml_id = 'module_category_' + ('_'.join(x.lower() for x in category)).replace('&', 'and').replace(' ', '_')
|
|
# search via xml_id (because some categories are renamed)
|
|
cr.execute("SELECT res_id FROM ir_model_data WHERE name=%s AND module=%s AND model=%s",
|
|
(xml_id, "base", "ir.module.category"))
|
|
|
|
c_id = cr.fetchone()
|
|
if not c_id:
|
|
cr.execute('INSERT INTO ir_module_category \
|
|
(name, parent_id) \
|
|
VALUES (%s, %s) RETURNING id', (categories[0], p_id))
|
|
c_id = cr.fetchone()[0]
|
|
cr.execute('INSERT INTO ir_model_data (module, name, res_id, model) \
|
|
VALUES (%s, %s, %s, %s)', ('base', xml_id, c_id, 'ir.module.category'))
|
|
else:
|
|
c_id = c_id[0]
|
|
p_id = c_id
|
|
categories = categories[1:]
|
|
return p_id
|
|
|
|
def has_unaccent(cr):
|
|
""" Test if the database has an unaccent function.
|
|
|
|
The unaccent is supposed to be provided by the PostgreSQL unaccent contrib
|
|
module but any similar function will be picked by OpenERP.
|
|
|
|
"""
|
|
cr.execute("SELECT proname FROM pg_proc WHERE proname='unaccent'")
|
|
return len(cr.fetchall()) > 0
|