From 26dee545aa96e3f6a96d34d57ddff46f1e4a1866 Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Wed, 23 Jun 2021 08:12:41 +0000 Subject: [PATCH] [FIX] website: unset menu/page m2o if menu url is a controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting a controller URL to a menu which is linked to a page should unset the m2o relationship to preserve the page URL. Step to reproduce: - Create a page (with a menu) and save, eg: "My Page" (url will be /my-page) - Edit that new menu and change the URL to /shop (or any controller) Issue: - The page URL is now /shop, which doesn't have a lot sense if there is a controller for that URL. - Unpublishing the website.page will unpublish the menu, as a menu needs its page to be published in order to be visible. Note: The flow introduced here is the same as if you choose an URL of an already existing page, the menu's page will be unlinked from the menu and left with its original URL. task-2575974 closes odoo/odoo#74163 X-original-commit: b52a01f76d89ebb7bdd75577e60a09f048c83000 Signed-off-by: Jérémy Kersten (jke) Signed-off-by: Romain Derie --- addons/website/models/website_menu.py | 9 ++- addons/website/tests/test_menu.py | 86 ++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/addons/website/models/website_menu.py b/addons/website/models/website_menu.py index 1192646ef44..bb08b51f90f 100644 --- a/addons/website/models/website_menu.py +++ b/addons/website/models/website_menu.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import werkzeug.exceptions + from odoo import api, fields, models from odoo.tools.translate import html_translate @@ -193,7 +195,12 @@ class Menu(models.Model): menu['page_id'] = page.id menu['url'] = page.url elif menu_id.page_id: - menu_id.page_id.write({'url': menu['url']}) + try: + # a page shouldn't have the same url as a controller + self.env['ir.http']._match(menu['url']) + menu_id.page_id = None + except werkzeug.exceptions.NotFound: + menu_id.page_id.write({'url': menu['url']}) menu_id.write(menu) return True diff --git a/addons/website/tests/test_menu.py b/addons/website/tests/test_menu.py index 672eea114d4..5a0fe9c4fc3 100644 --- a/addons/website/tests/test_menu.py +++ b/addons/website/tests/test_menu.py @@ -1,4 +1,7 @@ # coding: utf-8 + +import json + from odoo.tests import common @@ -7,7 +10,7 @@ class TestMenu(common.TransactionCase): super(TestMenu, self).setUp() self.nb_website = self.env['website'].search_count([]) - def test_menu_got_duplicated(self): + def test_01_menu_got_duplicated(self): Menu = self.env['website.menu'] total_menu_items = Menu.search_count([]) @@ -22,7 +25,7 @@ class TestMenu(common.TransactionCase): self.assertEqual(total_menu_items + self.nb_website * 2, Menu.search_count([]), "Creating a menu without a website_id should create this menu for every website_id") - def test_menu_count(self): + def test_02_menu_count(self): Menu = self.env['website.menu'] total_menu_items = Menu.search_count([]) @@ -47,7 +50,7 @@ class TestMenu(common.TransactionCase): self.assertEqual(total_menu_items + 2, Menu.search_count([]), "Creating 2 new menus should create only 2 menus records") - def test_default_menu_for_new_website(self): + def test_03_default_menu_for_new_website(self): Website = self.env['website'] Menu = self.env['website.menu'] total_menu_items = Menu.search_count([]) @@ -65,9 +68,8 @@ class TestMenu(common.TransactionCase): Website.create({'name': 'new website'}) self.assertEqual(total_menus + 4, Menu.search_count([]), "New website's bootstraping should have duplicate default menu tree (Top/Home/Contactus/Sub Default Menu)") - def test_specific_menu_translation(self): + def test_04_specific_menu_translation(self): Translation = self.env['ir.translation'] - Website = self.env['website'] Menu = self.env['website.menu'] existing_menus = Menu.search([]) @@ -77,7 +79,7 @@ class TestMenu(common.TransactionCase): 'name': 'Menu in english', 'url': 'turlututu', }) - new_menus = Menu.search([]) - existing_menus + new_menus = Menu.search([]) - existing_menus specific1, specific2 = new_menus.with_context(lang='fr_FR') - template_menu # create fr_FR translation for template menu @@ -86,8 +88,8 @@ class TestMenu(common.TransactionCase): Translation.search([ ('name', '=', 'website.menu,name'), ('res_id', '=', template_menu.id), ]).module = 'website' - self.assertEqual(specific1.name, 'Menu in english', - 'Translating template menu does not translate specific menu') + self.assertEqual(specific1.name, 'Menu in english', + 'Translating template menu does not translate specific menu') # have different translation for specific website specific1.name = 'Menu in french' @@ -95,21 +97,75 @@ class TestMenu(common.TransactionCase): # loading translation add missing specific translation Translation._load_module_terms(['website'], ['fr_FR']) Menu.invalidate_cache(['name']) - self.assertEqual(specific1.name, 'Menu in french', - 'Load translation without overwriting keep existing translation') - self.assertEqual(specific2.name, 'Menu en français', - 'Load translation add missing translation from template menu') + self.assertEqual(specific1.name, 'Menu in french', + 'Load translation without overwriting keep existing translation') + self.assertEqual(specific2.name, 'Menu en français', + 'Load translation add missing translation from template menu') # loading translation with overwrite sync all translations from menu template Translation._load_module_terms(['website'], ['fr_FR'], overwrite=True) Menu.invalidate_cache(['name']) - self.assertEqual(specific1.name, 'Menu en français', - 'Load translation with overwriting update existing menu from template') + self.assertEqual(specific1.name, 'Menu en français', + 'Load translation with overwriting update existing menu from template') - def test_default_menu_unlink(self): + def test_05_default_menu_unlink(self): Menu = self.env['website.menu'] total_menu_items = Menu.search_count([]) default_menu = self.env.ref('website.main_menu') default_menu.child_id[0].unlink() self.assertEqual(total_menu_items - 1 - self.nb_website, Menu.search_count([]), "Deleting a default menu item should delete its 'copies' (same URL) from website's menu trees. In this case, the default child menu and its copies on website 1 and website 2") + + +class TestMenuHttp(common.HttpCase): + def test_01_menu_page_m2o(self): + # 1. Create a page with a menu + Menu = self.env['website.menu'] + Page = self.env['website.page'] + page_url = '/page_specific' + page = Page.create({ + 'url': page_url, + 'website_id': 1, + # ir.ui.view properties + 'name': 'Base', + 'type': 'qweb', + 'arch': '
Specific View
', + 'key': 'test.specific_view', + }) + menu = Menu.create({ + 'name': 'Page Specific menu', + 'page_id': page.id, + 'url': page_url, + 'website_id': 1, + }) + + # 2. Edit the menu URL to a 'reserved' URL + data = { + 'id': menu.id, + 'parent_id': menu.parent_id.id, + 'name': menu.name, + 'url': '/website/info', + } + self.authenticate("admin", "admin") + # `Menu.save(1, {'data': [data], 'to_delete': []})` would have been + # ideal but need a full frontend context to generate routing maps, + # router and registry, even MockRequest is not enough + self.url_open('/web/dataset/call_kw', data=json.dumps({ + "params": { + 'model': 'website.menu', + 'method': 'save', + 'args': [1, {'data': [data], 'to_delete': []}], + 'kwargs': {}, + }, + }), headers={"Content-Type": "application/json"}) + + self.assertFalse(menu.page_id, "M2o should have been unset as this is a reserved URL.") + self.assertEqual(menu.url, '/website/info', "Menu URL should have changed.") + self.assertEqual(page.url, page_url, "Page's URL shouldn't have changed.") + + # 3. Edit the menu URL back to the page URL + data['url'] = page_url + Menu.save(1, {'data': [data], 'to_delete': []}) + self.assertEqual(menu.page_id, page, + "M2o should have been set back, as there was a page found with the new URL set on the menu.") + self.assertTrue(page.url == menu.url == page_url)