Skip to content

Commit 9a6f240

Browse files
committed
[IMP] remove and uninstall modules and themes recursively
Current implementation won't uninstall/remove dependencies. This can easily lead to a broken database, and is an unexpected and important divergence compared to the normal ORM uninstallation or removal process. @moduon MT-7110
1 parent c8c1a6e commit 9a6f240

File tree

1 file changed

+56
-17
lines changed

1 file changed

+56
-17
lines changed

src/util/modules.py

+56-17
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,44 @@ def module_installed(cr, module):
9898
return modules_installed(cr, module)
9999

100100

101-
def uninstall_module(cr, module):
101+
def module_dependencies(cr, module):
102+
"""Get dependencies of given module.
103+
104+
:param str module: name of the module
105+
:return: list names of the dependencies
106+
:rtype: list(str)
107+
"""
108+
cr.execute(
109+
"""
110+
SELECT m.name
111+
FROM ir_module_module m
112+
INNER JOIN ir_module_module_dependency d ON d.module_id = m.id
113+
WHERE d.name = %s
114+
""",
115+
[module],
116+
)
117+
return [name for (name,) in cr.fetchall()]
118+
119+
120+
def uninstall_module(cr, module, with_dependencies=False):
102121
"""
103122
Uninstall and remove all records owned by a module.
104123
105124
:param str module: name of the module to uninstall
125+
:param bool with_dependencies: whether to also remove dependencies of the module
126+
:return: set of uninstalled module names
127+
:rtype: set(str)
106128
"""
129+
result = set()
107130
cr.execute("SELECT id FROM ir_module_module WHERE name=%s", (module,))
108131
(mod_id,) = cr.fetchone() or [None]
109132
if not mod_id:
110-
return
133+
return result
134+
135+
if with_dependencies:
136+
dependencies = module_dependencies(cr, module)
137+
for dep in dependencies:
138+
result.union(uninstall_module(cr, dep, with_dependencies=with_dependencies))
111139

112140
# delete constraints only owned by this module
113141
cr.execute(
@@ -219,14 +247,18 @@ def uninstall_module(cr, module):
219247
if table_exists(cr, "ir_translation"):
220248
cr.execute("DELETE FROM ir_translation WHERE module=%s", [module])
221249
cr.execute("UPDATE ir_module_module SET state='uninstalled' WHERE name=%s", (module,))
250+
return result | {module}
222251

223252

224-
def uninstall_theme(cr, theme, base_theme=None):
253+
def uninstall_theme(cr, theme, base_theme=None, with_dependencies=False):
225254
"""
226255
Uninstall a theme module and remove it from websites.
227256
228257
:param str theme: name of the theme module to uninstall
229258
:param str or None base_theme: if not `None`, unload first this base theme
259+
:param bool with_dependencies: whether to also remove dependencies of the theme
260+
:return: set of uninstalled module names
261+
:rtype: set(str)
230262
231263
.. warning::
232264
@@ -238,7 +270,7 @@ def uninstall_theme(cr, theme, base_theme=None):
238270
cr.execute("SELECT id FROM ir_module_module WHERE name=%s AND state in %s", [theme, INSTALLED_MODULE_STATES])
239271
(theme_id,) = cr.fetchone() or [None]
240272
if not theme_id:
241-
return
273+
return None
242274

243275
env_ = env(cr)
244276
IrModuleModule = env_["ir.module.module"]
@@ -253,17 +285,20 @@ def uninstall_theme(cr, theme, base_theme=None):
253285
for website in websites:
254286
IrModuleModule._theme_remove(website)
255287
flush(env_["base"])
256-
uninstall_module(cr, theme)
288+
return uninstall_module(cr, theme, with_dependencies=with_dependencies)
257289

258290

259-
def remove_module(cr, module):
291+
def remove_module(cr, module, with_dependencies=False):
260292
"""
261293
Completely remove a module.
262294
263295
This operation is equivalent to uninstall and removal of *all* references to
264296
the module - no trace of it is left in the database.
265297
266298
:param str module: name of the module to remove
299+
:param bool with_dependencies: whether to also remove dependencies of the module
300+
:return: set of uninstalled module names
301+
:rtype: set(str)
267302
268303
.. warning::
269304
Since this function removes *all* data associated to the module. Ensure to
@@ -273,15 +308,17 @@ def remove_module(cr, module):
273308
# module need to be currently installed and running as deletions
274309
# are made using orm.
275310

276-
uninstall_module(cr, module)
277-
cr.execute("DELETE FROM ir_module_module_dependency WHERE name=%s", (module,))
278-
cr.execute("DELETE FROM ir_module_module WHERE name=%s RETURNING id", (module,))
311+
result = uninstall_module(cr, module, with_dependencies=with_dependencies)
312+
names = list(result)
313+
cr.execute("DELETE FROM ir_module_module_dependency WHERE name = ANY(%s)", (names,))
314+
cr.execute("DELETE FROM ir_module_module WHERE name = ANY(%s) RETURNING id", (names,))
279315
if cr.rowcount:
280-
[mod_id] = cr.fetchone()
281-
cr.execute("DELETE FROM ir_model_data WHERE model='ir.module.module' AND res_id=%s", [mod_id])
316+
ids = [id_ for (id_,) in cr.fetchall()]
317+
cr.execute("DELETE FROM ir_model_data WHERE model='ir.module.module' AND res_id = ANY(%s)", (ids,))
318+
return result
282319

283320

284-
def remove_theme(cr, theme, base_theme=None):
321+
def remove_theme(cr, theme, base_theme=None, with_dependencies=False):
285322
"""
286323
Uninstall a theme module.
287324
@@ -290,12 +327,14 @@ def remove_theme(cr, theme, base_theme=None):
290327
291328
See :func:`remove_module` and :func:`uninstall_theme`.
292329
"""
293-
uninstall_theme(cr, theme, base_theme=base_theme)
294-
cr.execute("DELETE FROM ir_module_module_dependency WHERE name=%s", (theme,))
295-
cr.execute("DELETE FROM ir_module_module WHERE name=%s RETURNING id", (theme,))
330+
result = uninstall_theme(cr, theme, base_theme=base_theme, with_dependencies=with_dependencies)
331+
themes = list(result)
332+
cr.execute("DELETE FROM ir_module_module_dependency WHERE name = ANY(%s)", (themes,))
333+
cr.execute("DELETE FROM ir_module_module WHERE name = ANY(%s) RETURNING id", (themes,))
296334
if cr.rowcount:
297-
[mod_id] = cr.fetchone()
298-
cr.execute("DELETE FROM ir_model_data WHERE model='ir.module.module' AND res_id=%s", [mod_id])
335+
ids = [id_ for (id_,) in cr.fetchall()]
336+
cr.execute("DELETE FROM ir_model_data WHERE model='ir.module.module' AND res_id = ANY(%s)", (ids,))
337+
return result
299338

300339

301340
def _update_view_key(cr, old, new):

0 commit comments

Comments
 (0)