diff options
| -rw-r--r-- | addons/website/models/ir_module_module.py | 10 | ||||
| -rw-r--r-- | addons/website/models/ir_ui_view.py | 8 | ||||
| -rw-r--r-- | addons/website/tests/test_views.py | 124 | ||||
| -rw-r--r-- | odoo/addons/base/models/ir_ui_view.py | 23 |
4 files changed, 132 insertions, 33 deletions
diff --git a/addons/website/models/ir_module_module.py b/addons/website/models/ir_module_module.py index 9ec53dcc519..62cddbf38a3 100644 --- a/addons/website/models/ir_module_module.py +++ b/addons/website/models/ir_module_module.py @@ -411,3 +411,13 @@ class IrModuleModule(models.Model): 'res_model': self._name, 'res_id': theme.id, }) + + def _check(self): + super()._check() + View = self.env['ir.ui.view'] + website_views_to_adapt = getattr(self.pool, 'website_views_to_adapt', []) + if website_views_to_adapt: + for view_replay in website_views_to_adapt: + cow_view = View.browse(view_replay[0]) + View._load_records_write_on_cow(cow_view, view_replay[1], view_replay[2]) + self.pool.website_views_to_adapt.clear() diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 69022920a39..c1d3a1304e1 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -131,6 +131,14 @@ class View(models.Model): return True + def _load_records_write_on_cow(self, cow_view, inherit_id, values): + inherit_id = self.search([ + ('key', '=', self.browse(inherit_id).key), + ('website_id', 'in', (False, cow_view.website_id.id)), + ], order='website_id', limit=1).id + values['inherit_id'] = inherit_id + cow_view.with_context(no_cow=True).write(values) + def _create_all_specific_views(self, processed_modules): """ When creating a generic child view, we should also create that view under specific view trees (COW'd). diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py index f07dfa898ee..d1bfea11166 100644 --- a/addons/website/tests/test_views.py +++ b/addons/website/tests/test_views.py @@ -13,7 +13,18 @@ def attrs(**kwargs): return {'data-oe-%s' % key: str(value) for key, value in kwargs.items()} -class TestViewSaving(common.TransactionCase): +class TestViewSavingCommon(common.TransactionCase): + def _create_imd(self, view): + xml_id = view.key.split('.') + return self.env['ir.model.data'].create({ + 'module': xml_id[0], + 'name': xml_id[1], + 'model': view._name, + 'res_id': view.id, + }) + + +class TestViewSaving(TestViewSavingCommon): def eq(self, a, b): self.assertEqual(a.tag, b.tag) @@ -43,6 +54,7 @@ class TestViewSaving(common.TransactionCase): self.view_id = self.env['ir.ui.view'].create({ 'name': "Test View", 'type': 'qweb', + 'key': 'website.test_view', 'arch': ET.tostring(self.arch, encoding='unicode') }) @@ -138,12 +150,7 @@ class TestViewSaving(common.TransactionCase): Company = self.env['res.company'] # create an xmlid for the view - imd = self.env['ir.model.data'].create({ - 'module': 'website', - 'name': 'test_view', - 'model': self.view_id._name, - 'res_id': self.view_id.id, - }) + imd = self._create_imd(self.view_id) self.assertEqual(self.view_id.model_data_id, imd) self.assertFalse(imd.noupdate) @@ -276,7 +283,7 @@ class TestViewSaving(common.TransactionCase): @tagged('-at_install', 'post_install') -class TestCowViewSaving(common.TransactionCase): +class TestCowViewSaving(TestViewSavingCommon): def setUp(self): super(TestCowViewSaving, self).setUp() View = self.env['ir.ui.view'] @@ -773,6 +780,7 @@ class TestCowViewSaving(common.TransactionCase): View._create_all_specific_views(['_website_sale_comparison']) specific_view = Website.with_context(load_all_views=True, website_id=1).viewref('_website_sale.product') + self.assertEqual(self.base_view.key, specific_view.key, "Ensure it is equal as it should be for the rest of the test so we test the expected behaviors") specific_view_arch = specific_view.read_combined(['arch'])['arch'] self.assertEqual(specific_view.website_id.id, 1, "Ensure we got specific view to perform the checks against") self.assertEqual(specific_view_arch, '<p>COMPARE</p>', "When a module creates an inherited view (on a generic tree), it should also create that view in the specific COW'd tree.") @@ -785,20 +793,33 @@ class TestCowViewSaving(common.TransactionCase): self.assertEqual(specific_view_arch, '<p>COMPARE EDITED</p>', "When a module updates an inherited view (on a generic tree), it should also update the copies of that view (COW).") # Test fields that should not be COW'd - random_view_id = View.search([], limit=1).id + random_views = View.search([('key', '!=', None)], limit=2) View._load_records([dict(xml_id='_website_sale_comparison.product_add_to_compare', values={ 'website_id': None, - 'inherit_id': random_view_id, + 'inherit_id': random_views[0].id, })]) w1_specific_child_view = Website.with_context(load_all_views=True, website_id=1).viewref('_website_sale_comparison.product_add_to_compare') generic_child_view = Website.with_context(load_all_views=True).viewref('_website_sale_comparison.product_add_to_compare') self.assertEqual(w1_specific_child_view.website_id.id, 1, "website_id is a prohibited field when COWing views during _load_records") - self.assertEqual(w1_specific_child_view.inherit_id.id != random_view_id, True, "inherit_id is a prohibited field when COWing views during _load_records") - self.assertEqual(generic_child_view.inherit_id.id, random_view_id, "prohibited fields only concerned write on COW'd view. Generic should still considere these fields") + self.assertEqual(generic_child_view.inherit_id, random_views[0], "prohibited fields only concerned write on COW'd view. Generic should still considere these fields") + self.assertEqual(w1_specific_child_view.inherit_id, random_views[0], "inherit_id update should be repliacated on cow views during _load_records") # Set back the generic view as parent for the rest of the test generic_child_view.inherit_id = self.base_view + w1_specific_child_view.inherit_id = specific_view + + # Don't update inherit_id if it was anually updated + w1_specific_child_view.inherit_id = random_views[1].id + View._load_records([dict(xml_id='_website_sale_comparison.product_add_to_compare', values={ + 'inherit_id': random_views[0].id, + })]) + self.assertEqual(w1_specific_child_view.inherit_id, random_views[1], + "inherit_id update should not be repliacated on cow views during _load_records if it was manually updated before") + + # Set back the generic view as parent for the rest of the test + generic_child_view.inherit_id = self.base_view + w1_specific_child_view.inherit_id = specific_view # Don't update fields from COW'd view if these fields have been modified from original view new_website = Website.create({'name': 'New Website'}) @@ -813,12 +834,7 @@ class TestCowViewSaving(common.TransactionCase): self.assertEqual(new_website_specific_child_view.priority, 6, "XML update should NOT be written on the specific view if the fields have been modified on that specific view") # Simulate website_sale update on top level view - self.env['ir.model.data'].create({ - 'module': '_website_sale', - 'name': 'product', - 'model': self.base_view._name, - 'res_id': self.base_view.id, - }) + self._create_imd(self.base_view) self.base_view.invalidate_cache() View._load_records([dict(xml_id='_website_sale.product', values={ 'website_meta_title': 'A bug got fixed by updating this field', @@ -920,12 +936,7 @@ class TestCowViewSaving(common.TransactionCase): 'arch': '<div>base content</div>', 'key': 'website.base_view_v1', }).with_context(load_all_views=True) - self.env['ir.model.data'].create({ - 'module': 'website', - 'name': 'base_view_v1', - 'model': v1._name, - 'res_id': v1.id, - }) + self._create_imd(v1) # Extension v2 = View.create({ @@ -935,12 +946,7 @@ class TestCowViewSaving(common.TransactionCase): 'arch': '<div position="inside"><ooo>extended content</ooo></div>', 'key': 'website.extension_view_v2', }) - self.env['ir.model.data'].create({ - 'module': 'website', - 'name': 'extension_view_v2', - 'model': v2._name, - 'res_id': v2.id, - }) + self._create_imd(v2) # multiwebsite specific v1.with_context(website_id=1).write({'name': 'Extension Specific'}) @@ -986,6 +992,64 @@ class TestCowViewSaving(common.TransactionCase): self.assertEqual(specific_view.with_context(lang='en_US').arch, '<div>hi</div>', "loading module translation copy translation from base to specific view") + def test_specific_view_module_update_inherit_change(self): + """ During a module update, if inherit_id is changed, we need to + replicate the change for cow views. """ + # If D.inherit_id becomes B instead of A, after module update, we expect: + # CASE 1 + # A A' B A A' B + # | | => / \ + # D D' D D' + # + # CASE 2 + # A A' B B' A A' B B' + # | | => | | + # D D' D D' + # + # CASE 3 + # A B A B + # / \ => / \ + # D D' D D' + # + # CASE 4 + # A B B' A B B' + # / \ => | | + # D D' D D' + + # 1. Setup following view trees + # A A' B + # | | + # D D' + View = self.env['ir.ui.view'] + Website = self.env['website'] + self._create_imd(self.inherit_view) + # invalidate cache to recompute xml_id, or it will still be empty + self.inherit_view.invalidate_cache() + base_view_2 = self.base_view.copy({'key': 'website.base_view2', 'arch': '<div>base2 content</div>'}) + self.base_view.with_context(website_id=1).write({'arch': '<div>website 1 content</div>'}) + specific_view = Website.with_context(load_all_views=True, website_id=1).viewref(self.base_view.key) + specific_view.inherit_children_ids.with_context(website_id=1).write({'arch': '<div position="inside">, extended content website 1</div>'}) + specific_child_view = Website.with_context(load_all_views=True, website_id=1).viewref(self.inherit_view.key) + # 2. Ensure view trees are as expected + self.assertEqual(self.base_view.inherit_children_ids, self.inherit_view, "D should be under A") + self.assertEqual(specific_view.inherit_children_ids, specific_child_view, "D' should be under A'") + self.assertFalse(base_view_2.inherit_children_ids, "B should have no child") + + # 3. Simulate module update, D.inherit_id is now B instead of A + View._load_records([dict(xml_id=self.inherit_view.key, values={ + 'inherit_id': base_view_2.id, + })]) + + # 4. Ensure view trees is now + # A A' B + # / \ + # D D' + self.assertTrue(len(self.base_view.inherit_children_ids) == len(specific_view.inherit_children_ids) == 0, + "Child views should now be under view B") + self.assertEqual(len(base_view_2.inherit_children_ids), 2, "D and D' should be under B") + self.assertTrue(self.inherit_view in base_view_2.inherit_children_ids, "D should be under B") + self.assertTrue(specific_child_view in base_view_2.inherit_children_ids, "D' should be under B") + @tagged('-at_install', 'post_install') class Crawler(HttpCase): diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index c77316d4557..a042859dc82 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -1826,15 +1826,32 @@ actual arch. noupdate behavior on views having an ir.model.data. """ if self.type == 'qweb': - # Update also specific views for cow_view in self._get_specific_views(): authorized_vals = {} for key in values: - if cow_view[key] == self[key]: + if key != 'inherit_id' and cow_view[key] == self[key]: authorized_vals[key] = values[key] - cow_view.write(authorized_vals) + # if inherit_id update, replicate change on cow view but + # only if that cow view inherit_id wasn't manually changed + inherit_id = values.get('inherit_id') + if inherit_id and self.inherit_id.id != inherit_id and \ + cow_view.inherit_id.key == self.inherit_id.key: + self._load_records_write_on_cow(cow_view, inherit_id, authorized_vals) + else: + cow_view.with_context(no_cow=True).write(authorized_vals) super(View, self)._load_records_write(values) + def _load_records_write_on_cow(self, cow_view, inherit_id, values): + # for modules updated before `website`, we need to + # store the change to replay later on cow views + if not hasattr(self.pool, 'website_views_to_adapt'): + self.pool.website_views_to_adapt = [] + self.pool.website_views_to_adapt.append(( + cow_view.id, + inherit_id, + values, + )) + class ResetViewArchWizard(models.TransientModel): """ A wizard to compare and reset views architecture. """ |
