summaryrefslogtreecommitdiff
path: root/addons/website_theme_install
diff options
context:
space:
mode:
authorRomain Derie <rde@odoo.com>2018-12-07 10:38:37 +0000
committerRomain Derie <rde@odoo.com>2018-12-19 18:23:36 +0000
commitf7df35540b8404a14e8d29af80b8e8642a4c1bdb (patch)
tree3863f5d566a5dbdcf01b742cf45b20242d52e1d9 /addons/website_theme_install
parenteb19016ba35d4dc4723edcc244fd412680b5ff2f (diff)
[FIX] web_editor, website(_theme_install): avoid theme.ir.ui.view crash
In a particular case, `_views_get` was mixing apples and oranges, ir.ui.view and theme.ir.ui.view. This was the case if a theme has a theme.ir.ui.view t-calling another theme.ir.ui.view. (Only theme's sale bridge cause this issue, eg theme_kea_sale.products) Then, having that theme installed on a website would make all other website without that theme to crash when opening the /shop customize menu. Indeed, the theme.ir.ui.view would have the xml_id theme_kea_sale.search. The generic view `website_sale.products` would have `theme_kea_sale.products` as inherit child which is website specific and t-call `theme_kea_sale.search`. Then, on a website witout theme_kea installed, opening the customize menu would call `get_related_views` that would call `_views_get` that would loop through inherit children calling `_view_obj` that would not find a view with that key and would fallback on self.env.ref, finding the theme.ir.ui.view. Note: `_views_get` might return specific view for another website but that will be filtered on `get_related_views` override in website. Step to reproduce: - Have at least 2 websites and website_sale installed - Install theme_kea on a website, it will also install theme_kea_sale - Go on the website that has not theme_kea installed - Go to /shop - Open Customize menu, it won't load because of python crash opw-1914576 Closes #29347
Diffstat (limited to 'addons/website_theme_install')
-rw-r--r--addons/website_theme_install/tests/__init__.py4
-rw-r--r--addons/website_theme_install/tests/test_views.py151
2 files changed, 155 insertions, 0 deletions
diff --git a/addons/website_theme_install/tests/__init__.py b/addons/website_theme_install/tests/__init__.py
new file mode 100644
index 00000000000..28efb5bbfb5
--- /dev/null
+++ b/addons/website_theme_install/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_views
diff --git a/addons/website_theme_install/tests/test_views.py b/addons/website_theme_install/tests/test_views.py
new file mode 100644
index 00000000000..7657dcb19b8
--- /dev/null
+++ b/addons/website_theme_install/tests/test_views.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.tests import HttpCase
+
+
+class Crawler(HttpCase):
+ def test_multi_website_views_retrieving(self):
+ View = self.env['ir.ui.view']
+ Website = self.env['website']
+
+ website_1 = Website.create({'name': 'Website 1'})
+ website_2 = Website.create({'name': 'Website 2'})
+
+ main_view = View.create({
+ 'name': 'Products',
+ 'type': 'qweb',
+ 'arch': '<body>Arch is not relevant for this test</body>',
+ 'key': '_website_sale.products',
+ }).with_context(load_all_views=True)
+
+ View.with_context(load_all_views=True).create({
+ 'name': 'Child View W1',
+ 'mode': 'extension',
+ 'inherit_id': main_view.id,
+ 'arch': '<xpath expr="//body" position="replace">It is really not relevant!</xpath>',
+ 'key': '_website_sale.child_view_w1',
+ 'website_id': website_1.id,
+ 'active': False,
+ 'customize_show': True,
+ })
+
+ # Simulate theme view instal + load on website
+ theme_view = self.env['theme.ir.ui.view'].with_context(install_filename='/testviews').create({
+ 'name': 'Products Theme Kea',
+ 'mode': 'extension',
+ 'inherit_id': main_view.id,
+ 'arch': '<xpath expr="//p" position="replace"><span>C</span></xpath>',
+ 'key': '_theme_kea_sale.products',
+ })
+ view_from_theme_view_on_w2 = View.with_context(load_all_views=True).create({
+ 'name': 'Products Theme Kea',
+ 'mode': 'extension',
+ 'inherit_id': main_view.id,
+ 'arch': '<xpath expr="//body" position="replace">Really really not important for this test</xpath>',
+ 'key': '_theme_kea_sale.products',
+ 'website_id': website_2.id,
+ 'customize_show': True,
+ })
+ self.env['ir.model.data'].create({
+ 'module': '_theme_kea_sale',
+ 'name': 'products',
+ 'model': 'theme.ir.ui.view',
+ 'res_id': theme_view.id,
+ })
+
+ # ##################################################### ir.ui.view ###############################################
+ # id | name | website_id | inherit | key | xml_id |
+ # ----------------------------------------------------------------------------------------------------------------
+ # 1 | Products | / | / | _website_sale.products | / |
+ # 2 | Child View W1 | 1 | 1 | _website_sale.child_view_w1 | / |
+ # 3 | Products Theme Kea | 2 | 1 | _theme_kea_sale.products | / |
+
+ # ################################################# theme.ir.ui.view #############################################
+ # id | name | inherit | key | xml_id |
+ # ----------------------------------------------------------------------------------------------------------------
+ # 1 | Products Theme Kea | 1 | _theme_kea_sale.products | _theme_kea_sale.products |
+
+ with self.assertRaises(ValueError):
+ # It should crash as it should not find a view on website 1 for '_theme_kea_sale.products', !!and certainly not a theme.ir.ui.view!!.
+ view = View.with_context(website_id=website_1.id)._view_obj('_theme_kea_sale.products')
+ view = View.with_context(website_id=website_2.id)._view_obj('_theme_kea_sale.products')
+ self.assertEquals(len(view), 1, "It should find the ir.ui.view with key '_theme_kea_sale.products' on website 2..")
+ self.assertEquals(view._name, 'ir.ui.view', "..and not a theme.ir.ui.view")
+
+ views = View.with_context(website_id=website_1.id).get_related_views('_website_sale.products')
+ self.assertEquals(len(views), 2, "It should not mix apples and oranges, only ir.ui.view ['_website_sale.products', '_website_sale.child_view_w1'] should be returned")
+ views = View.with_context(website_id=website_2.id).get_related_views('_website_sale.products')
+ self.assertEquals(len(views), 2, "It should not mix apples and oranges, only ir.ui.view ['_website_sale.products', '_theme_kea_sale.products'] should be returned")
+
+ # Part 2 of the test, it test the same stuff but from a higher level (get_related_views ends up calling _view_obj)
+ called_theme_view = self.env['theme.ir.ui.view'].with_context(install_filename='/testviews').create({
+ 'name': 'Called View Kea',
+ 'arch': '<div></div>',
+ 'key': '_theme_kea_sale.t_called_view',
+ })
+ View.create({
+ 'name': 'Called View Kea',
+ 'type': 'qweb',
+ 'arch': '<div></div>',
+ 'key': '_theme_kea_sale.t_called_view',
+ 'website_id': website_2.id,
+ }).with_context(load_all_views=True)
+ self.env['ir.model.data'].create({
+ 'module': '_theme_kea_sale',
+ 'name': 't_called_view',
+ 'model': 'theme.ir.ui.view',
+ 'res_id': called_theme_view.id,
+ })
+ view_from_theme_view_on_w2.write({'arch': '<t t-call="_theme_kea_sale.t_called_view"/>'})
+
+ # ##################################################### ir.ui.view ###############################################
+ # id | name | website_id | inherit | key | xml_id |
+ # ----------------------------------------------------------------------------------------------------------------
+ # 1 | Products | / | / | _website_sale.products | / |
+ # 2 | Child View W1 | 1 | 1 | _website_sale.child_view_w1 | / |
+ # 3 | Products Theme Kea | 2 | 1 | _theme_kea_sale.products | / |
+ # 4 | Called View Kea | 2 | / | _theme_kea_sale.t_called_view | / |
+
+ # ################################################# theme.ir.ui.view #############################################
+ # id | name | inherit | key | xml_id |
+ # ----------------------------------------------------------------------------------------------------------------
+ # 1 | Products Theme Kea | 1 | _theme_kea_sale.products | _theme_kea_sale.products |
+ # 1 | Called View Kea | / | _theme_kea_sale.t_called_view | _theme_kea_sale.t_called_view |
+
+ # Next line should not crash (was mixing apples and oranges - ir.ui.view and theme.ir.ui.view)
+ views = View.with_context(website_id=website_1.id).get_related_views('_website_sale.products')
+ self.assertEquals(len(views), 2, "It should not mix apples and oranges, only ir.ui.view ['_website_sale.products', '_website_sale.child_view_w1'] should be returned (2)")
+ views = View.with_context(website_id=website_2.id).get_related_views('_website_sale.products')
+ self.assertEquals(len(views), 3, "It should not mix apples and oranges, only ir.ui.view ['_website_sale.products', '_theme_kea_sale.products', '_theme_kea_sale.t_called_view'] should be returned")
+
+ # ########################################################
+ # Test the controller (which is calling get_related_views)
+ self.authenticate("admin", "admin")
+ base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
+
+ # Simulate website 2
+ url = base_url + '/website/force_website'
+ json = {'params': {'website_id': website_2.id}}
+ self.opener.post(url=url, json=json)
+
+ # Test controller
+ url = base_url + '/website/get_switchable_related_views'
+ json = {'params': {'key': '_website_sale.products'}}
+ response = self.opener.post(url=url, json=json)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.json()['result']), 1, "Only '_theme_kea_sale.products' should be returned as it is the only customize_show related view in website 2 context")
+ self.assertEqual(response.json()['result'][0]['key'], '_theme_kea_sale.products', "Only '_theme_kea_sale.products' should be returned")
+
+ # Simulate website 1
+ url = base_url + '/website/force_website'
+ json = {'params': {'website_id': website_1.id}}
+ self.opener.post(url=url, json=json)
+
+ # Test controller
+ url = base_url + '/website/get_switchable_related_views'
+ json = {'params': {'key': '_website_sale.products'}}
+ response = self.opener.post(url=url, json=json)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.json()['result']), 1, "Only '_website_sale.child_view_w1' should be returned as it is the only customize_show related view in website 1 context")
+ self.assertEqual(response.json()['result'][0]['key'], '_website_sale.child_view_w1', "Only '_website_sale.child_view_w1' should be returned")