diff --git a/src/base/tests/test_util.py b/src/base/tests/test_util.py
index 97d00dc8c..84e96e08e 100644
--- a/src/base/tests/test_util.py
+++ b/src/base/tests/test_util.py
@@ -1825,3 +1825,101 @@ def test_convert_field_to_html(self):
self.assertEqual(default.json_value, '"
Test text
"')
self.assertEqual(partner.x_testx, "test partner field
")
+
+
+class TestRemoveView(UnitTestCase):
+ def test_remove_view(self):
+ test_view_1 = self.env["ir.ui.view"].create(
+ {
+ "name": "test_view_1",
+ "type": "qweb",
+ "key": "base.test_view_1",
+ "arch": """
+
+ Test View 1 Content
+
+ """,
+ }
+ )
+ self.env["ir.model.data"].create(
+ {"name": "test_view_1", "module": "base", "model": "ir.ui.view", "res_id": test_view_1.id}
+ )
+ test_view_2 = self.env["ir.ui.view"].create(
+ {
+ "name": "test_view_2",
+ "type": "qweb",
+ "key": "base.test_view_2",
+ "arch": """
+
+
+ Test View 2 Content
+
+ """,
+ }
+ )
+ test_view_3 = self.env["ir.ui.view"].create(
+ {
+ "name": "test_view_3",
+ "type": "qweb",
+ "key": "base.test_view_3",
+ "arch": """
+
+
+
+
+ """,
+ }
+ )
+ self.env["ir.model.data"].create(
+ {"name": "test_view_3", "module": "base", "model": "ir.ui.view", "res_id": test_view_3.id}
+ )
+
+ # call by xml_id
+ util.remove_view(self.env.cr, xml_id="base.test_view_1")
+ util.invalidate(test_view_2)
+ util.invalidate(test_view_3)
+ self.assertFalse(test_view_1.exists())
+ self.assertNotIn('t-call="base.test_view_1"', test_view_2.arch_db)
+ self.assertNotIn('t-call="base.test_view_1"', test_view_3.arch_db)
+
+ # call by view_id
+ util.remove_view(self.env.cr, view_id=test_view_2.id)
+ util.invalidate(test_view_3)
+ self.assertFalse(test_view_2.exists())
+ self.assertNotIn('t-call="base.test_view_2"', test_view_3.arch_db)
+
+
+class TestRenameXMLID(UnitTestCase):
+ def test_rename_xmlid(self):
+ test_view_1 = self.env["ir.ui.view"].create(
+ {
+ "name": "test_view_1",
+ "type": "qweb",
+ "key": "base.test_view_1",
+ "arch": """
+
+ Test View 1 Content
+
+ """,
+ }
+ )
+ self.env["ir.model.data"].create(
+ {"name": "test_view_1", "module": "base", "model": "ir.ui.view", "res_id": test_view_1.id}
+ )
+ test_view_2 = self.env["ir.ui.view"].create(
+ {
+ "name": "test_view_2",
+ "type": "qweb",
+ "key": "base.test_view_2",
+ "arch": """
+
+
+ Test View 2 Content
+
+ """,
+ }
+ )
+ util.rename_xmlid(self.env.cr, "base.test_view_1", "base.rename_view")
+ util.invalidate(test_view_2)
+ self.assertIn('t-call="base.rename_view"', test_view_2.arch_db)
+ self.assertIn('t-name="base.rename_view"', test_view_1.arch_db)
diff --git a/src/util/records.py b/src/util/records.py
index cc4474cbc..a0691c79f 100644
--- a/src/util/records.py
+++ b/src/util/records.py
@@ -12,12 +12,12 @@
from psycopg2.extras import Json, execute_values
try:
- from odoo import release
+ from odoo import modules, release
from odoo.tools.convert import xml_import
from odoo.tools.misc import file_open
from odoo.tools.translate import xml_translate
except ImportError:
- from openerp import release
+ from openerp import modules, release
from openerp.tools.convert import xml_import
from openerp.tools.misc import file_open
@@ -106,6 +106,16 @@ def remove_view(cr, xml_id=None, view_id=None, silent=False, key=None):
for [v_id] in cr.fetchall():
remove_view(cr, view_id=v_id, silent=silent, key=xml_id)
+ if not key and column_exists(cr, "ir_ui_view", "key"):
+ cr.execute("SELECT key FROM ir_ui_view WHERE id = %s and key != %s", [view_id, xml_id])
+ [key] = cr.fetchone() or [None]
+
+ # Occurrences of xml_id and key in the t-call of views are to be found and removed.
+ if xml_id != "?":
+ _remove_redundant_tcalls(cr, xml_id)
+ if key and key != xml_id:
+ _remove_redundant_tcalls(cr, key)
+
if not view_id:
return
@@ -794,6 +804,34 @@ def rename_xmlid(cr, old, new, noupdate=None, on_collision="fail"):
# Don't change the view keys unconditionally to avoid changing unrelated views.
cr.execute("UPDATE ir_ui_view SET key = %s WHERE key = %s", [new, old])
+ # Adapting t-call and t-name references in views
+ search_pattern = r"""\yt-(call|name)=(["']){}\2""".format(re.escape(old))
+ replace_pattern = r"t-\1=\2{}\2".format(new)
+ if version_gte("16.0"):
+ arch_col = get_value_or_en_translation(cr, "ir_ui_view", "arch_db")
+ replace_in_all_jsonb_values(
+ cr,
+ "ir_ui_view",
+ "arch_db",
+ PGRegexp(search_pattern),
+ replace_pattern,
+ extra_filter=cr.mogrify("{} ~ %s".format(arch_col), [search_pattern]).decode(),
+ )
+ else:
+ arch_col = "arch_db" if column_exists(cr, "ir_ui_view", "arch_db") else "arch"
+ cr.execute(
+ format_query(
+ cr,
+ """
+ UPDATE ir_ui_view
+ SET {arch} = regexp_replace({arch}, %s, %s, 'g')
+ WHERE {arch} ~ %s
+ """,
+ arch=arch_col,
+ ),
+ [search_pattern, replace_pattern, search_pattern],
+ )
+
if model == "ir.ui.menu" and column_exists(cr, "res_users_settings", "homemenu_config"):
query = """
UPDATE res_users_settings
@@ -1784,3 +1822,47 @@ def remove_act_window_view_mode(cr, model, view_mode):
""",
[view_mode, default, model, view_mode, view_mode],
)
+
+
+def _remove_redundant_tcalls(cr, match):
+ """
+ Remove t-calls of the removed view.
+
+ This function removes the t-calls to `match`.
+
+ :param str match: t-calls value to remove, typically it would be a view's xml_id or key
+ """
+ arch_col = (
+ get_value_or_en_translation(cr, "ir_ui_view", "arch_db")
+ if column_exists(cr, "ir_ui_view", "arch_db")
+ else "arch"
+ )
+ cr.execute(
+ format_query(
+ cr,
+ """
+ SELECT iv.id,
+ imd.module,
+ imd.name
+ FROM ir_ui_view iv
+ LEFT JOIN ir_model_data imd
+ ON iv.id = imd.res_id
+ AND imd.model = 'ir.ui.view'
+ WHERE {} ~ %s
+ """,
+ sql.SQL(arch_col),
+ ),
+ [r"""\yt-call=(["']){}\1""".format(re.escape(match))],
+ )
+ standard_modules = set(modules.get_modules()) - {"studio_customization"}
+ for vid, module, name in cr.fetchall():
+ with edit_view(cr, view_id=vid) as arch:
+ for node in arch.findall(".//t[@t-call='{}']".format(match)):
+ node.getparent().remove(node)
+ if not module or module not in standard_modules:
+ _logger.info(
+ "The view %swith ID: %s has been updated, removed t-calls to deprecated %r",
+ ("`{}.{}` ".format(module, name) if module else ""),
+ vid,
+ match,
+ )