diff --git a/addons/auth_signup/models/res_config_settings.py b/addons/auth_signup/models/res_config_settings.py index f0b9c1385f4..c9ea615e827 100644 --- a/addons/auth_signup/models/res_config_settings.py +++ b/addons/auth_signup/models/res_config_settings.py @@ -11,8 +11,8 @@ class ResConfigSettings(models.TransientModel): auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page', config_parameter='auth_signup.reset_password') auth_signup_uninvited = fields.Selection([ - ('b2b', 'On invitation (B2B)'), - ('b2c', 'Free sign up (B2C)'), + ('b2b', 'On invitation'), + ('b2c', 'Free sign up'), ], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope') auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup', config_parameter='base.template_portal_user_id') diff --git a/addons/sale/models/res_config_settings.py b/addons/sale/models/res_config_settings.py index 6bd63b6d094..b99e547d6ad 100644 --- a/addons/sale/models/res_config_settings.py +++ b/addons/sale/models/res_config_settings.py @@ -52,8 +52,8 @@ class ResConfigSettings(models.TransientModel): module_website_sale_digital = fields.Boolean("Sell digital products - provide downloadable content on your customer portal") auth_signup_uninvited = fields.Selection([ - ('b2b', 'On invitation (B2B)'), - ('b2c', 'Free sign up (B2C)'), + ('b2b', 'On invitation'), + ('b2c', 'Free sign up'), ], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope') module_delivery = fields.Boolean("Shipping Costs") diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index 2c5c131f5e7..4842dab2625 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -10,7 +10,8 @@ - Top Menu + Top Menu + @@ -18,6 +19,7 @@ / 10 + @@ -25,6 +27,7 @@ /contactus 60 + diff --git a/addons/website/data/website_demo.xml b/addons/website/data/website_demo.xml index d03a65ce01d..61bda1e784e 100644 --- a/addons/website/data/website_demo.xml +++ b/addons/website/data/website_demo.xml @@ -176,7 +176,7 @@ response = request.render("website.template_partner_comment", { Contact us /contactus - + 60 diff --git a/addons/website/models/ir_attachment.py b/addons/website/models/ir_attachment.py index 7bbcea66607..3225fa0918f 100644 --- a/addons/website/models/ir_attachment.py +++ b/addons/website/models/ir_attachment.py @@ -13,6 +13,13 @@ class Attachment(models.Model): # related for backward compatibility with saas-6 website_url = fields.Char(string="Website URL", related='local_url', deprecated=True) + @api.model + def create(self, vals): + website = self.env['website'].get_current_website() + if website and 'website_id' not in vals and 'not_force_website_id' not in self.env.context: + vals['website_id'] = website.id + return super(Attachment, self).create(vals) + @api.model def get_serving_groups(self): return super(Attachment, self).get_serving_groups() + ['website.group_website_designer'] diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 2a55e609199..620e2cfd739 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -179,7 +179,7 @@ class Http(models.AbstractModel): return response except Exception as e: if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, QWebException) or not exception.qweb.get('cause')): - raise + raise e exception = e values = dict( diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 50f61c33909..3148bb984eb 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -75,9 +75,8 @@ class View(models.Model): # reuse the COW mechanism to create # website-specific copies, it will take # care of creating pages and menus. - view.with_context(website_id=website.id).write({'key': '%s [website %s]' % (view.key, website.id)}) + view.with_context(website_id=website.id).write({'name': view.name}) - self |= self.with_context(active_test=False).search([('key', 'in', self.filtered('key').mapped('key'))]) result = super(View, self).unlink() self.clear_caches() return result @@ -108,9 +107,6 @@ class View(models.Model): new_page = page.copy({ 'view_id': new_view.id, }) - for menu in page.menu_ids: - # trigger COW - menu.write({'page_id': new_page.id}) @api.model def get_related_views(self, key, bundles=False): diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 5a927dec45e..e8eea753f80 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -99,15 +99,15 @@ class Website(models.Model): installed_theme_id = fields.Many2one('ir.module.module') theme_ids = fields.Many2many('ir.module.module', 'website_theme', 'website_id', 'ir_module_module_id') auth_signup_uninvited = fields.Selection([ - ('b2b', 'On invitation (B2B)'), - ('b2c', 'Free sign up (B2C)'), + ('b2b', 'On invitation'), + ('b2c', 'Free sign up'), ], string='Customer Account', default='b2b') @api.multi def _compute_menu(self): Menu = self.env['website.menu'] for website in self: - website.menu_id = Menu.search([('parent_id', '=', False), ('website_id', 'in', (website.id, False))], order='id', limit=1).id + website.menu_id = Menu.search([('parent_id', '=', False), ('website_id', '=', website.id)], order='id', limit=1).id # cf. Wizard hack in website_views.xml def noop(self, *args, **kwargs): @@ -162,6 +162,18 @@ class Website(models.Model): self.homepage_id = self.env['website.page'].search([('website_id', '=', self.id), ('key', '=', standard_homepage.key)]) + top_menu = self.env['website.menu'].create({ + 'name': 'Top Menu for website ' + str(self.id), + 'website_id': self.id, + 'sequence': 0 + }) + self.menu_id = top_menu.id + self.env['website.menu'].create({ + 'name': 'Home', 'url': '/', + 'website_id': self.id, + 'parent_id': top_menu.id, + 'sequence': 10 + }) @api.model def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None): @@ -196,10 +208,11 @@ class Website(models.Model): if view.arch_fs: view.arch_fs = False + website = self.get_current_website() if ispage: page = self.env['website.page'].create({ 'url': page_url, - 'website_id': self.get_current_website().id, + 'website_id': website.id, # remove it if only one webiste or not? 'view_id': view.id, }) result['view_id'] = view.id @@ -207,9 +220,9 @@ class Website(models.Model): self.env['website.menu'].create({ 'name': name, 'url': page_url, - 'parent_id': self.get_current_website().menu_id.id, + 'parent_id': website.menu_id.id, 'page_id': page.id, - 'website_id': self.get_current_website().id, + 'website_id': website.id, }) return result @@ -222,7 +235,10 @@ class Website(models.Model): :param page_url : the url to be checked for uniqueness """ inc = 0 - domain_static = self.get_current_website().website_domain() + # we only want a unique_path for website specific. + # we need to be able to have /url for website=False, and /url for website=1 + # in case of duplicate, page manager will allow you to manage this case + domain_static = [('website_id', '=', self.get_current_website().id)] # .website_domain() page_temp = page_url while self.env['website.page'].with_context(active_test=False).sudo().search([('url', '=', page_temp)] + domain_static): inc += 1 @@ -877,7 +893,10 @@ class Page(models.Model): # is copied from. # (eg: website_version: an ir.ui.view record with the same key is # expected to be the same ir.ui.view but from another version) - new_view = view.copy({'key': view.key + '.copy', 'name': '%s %s' % (view.name, _('(copy)'))}) + website_id = default.get('website_id') + # website_id should be set during copy to avoid copying a generic page to another generic copy and then write on it + # which would result in the generic page copied to a generic one and a specific one + new_view = view.copy({'key': view.key + '.copy', 'name': '%s %s' % (view.name, _('(copy)')), 'website_id': website_id.id}) default = { 'name': '%s %s' % (self.name, _('(copy)')), 'url': self.env['website'].get_unique_path(self.url), @@ -891,8 +910,10 @@ class Page(models.Model): :param page_id : website.page identifier """ page = self.browse(int(page_id)) - new_page = page.copy() - if clone_menu: + new_page = page.copy(dict(website_id=self.env['website'].get_current_website())) + # Should not clone menu if the page was cloned from one website to another + # Eg: Cloning a generic page (no website) will create a page with a website, we can't clone menu (not same container) + if clone_menu and new_page.website_id == page.website_id: menu = self.env['website.menu'].search([('page_id', '=', page_id)], limit=1) if menu: # If the page being cloned has a menu, clone it too @@ -909,7 +930,7 @@ class Page(models.Model): for page in self: # Other pages linked to the ir_ui_view of the page being deleted (will it even be possible?) pages_linked_to_iruiview = self.search( - [('view_id', '=', self.view_id.id), ('id', '!=', self.id)] + [('view_id', '=', page.view_id.id), ('id', '!=', page.id)] ) if len(pages_linked_to_iruiview) == 0 and not page.view_id.inherit_children_ids: # If there is no other pages linked to that ir_ui_view, we can delete the ir_ui_view @@ -946,37 +967,27 @@ class Menu(models.Model): parent_path = fields.Char(index=True) is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible') - @api.multi - def unlink(self): - '''This implements COU (copy-on-unlink). When deleting a generic menu - item website-specific menu items will be created.''' - current_website_id = self._context.get('website_id') - - if current_website_id and not self._context.get('no_cow'): - for menu in self.filtered(lambda menu: not menu.website_id): - for website in self.env['website'].search([('id', '!=', current_website_id)]): - # reuse the COW mechanism to create website-specific copies - menu.with_context(website_id=website.id).write({}) - - return super(Menu, self).unlink() - - @api.multi - def write(self, vals): - '''This implements COW (copy-on-write). This way editing websites does - not impact other websites and newly created websites will only - contain the default menus. + @api.model + def create(self, vals): + ''' In case a menu without a website_id is trying to be created, we duplicate + it for every website. + Note: Particulary useful when installing a module that adds a menu like + /shop. So every website has the shop menu. ''' - current_website_id = self._context.get('website_id') - - if current_website_id and not self.website_id and not self._context.get('no_cow'): - new_website_specific_menu = self.copy({'website_id': current_website_id}) - - for child in self.child_id: - child.write({'parent_id': new_website_specific_menu.id}) - - return new_website_specific_menu.write(vals) - - return super(Menu, self).write(vals) + if vals.get('website_id'): + return super(Menu, self).create(vals) + elif self._context.get('website_id'): + vals.update({'website_id': self._context.get('website_id')}) + return super(Menu, self).create(vals) + else: + # create for every site + for website in self.env['website'].search([]): + vals.update({ + 'website_id': website.id, + 'parent_id': website.menu_id.id, + }) + res = super(Menu, self).create(vals) + return res # create loop, what to return ? last created record ? @api.one def _compute_visible(self): @@ -1000,21 +1011,6 @@ class Menu(models.Model): url = '/%s' % self.url return url - def get_children_for_current_website(self): - most_specific_child_menus = self.env['website.menu'] - website_id = self._context.get('website_id') - - if not website_id: - return self.child_id - - for child in self.child_id: - if child.website_id and child.website_id.id == website_id: - most_specific_child_menus |= child - elif not child.website_id and not any(child.clean_url() == child2.clean_url() and child2.website_id.id == website_id for child2 in self.child_id): - most_specific_child_menus |= child - - return most_specific_child_menus - # would be better to take a menu_id as argument @api.model def get_tree(self, website_id, menu_id=None): @@ -1031,7 +1027,7 @@ class Menu(models.Model): children=[], is_homepage=is_homepage, ) - for child in node.get_children_for_current_website(): + for child in node.child_id: menu_node['children'].append(make_tree(child)) return menu_node if menu_id: @@ -1055,7 +1051,7 @@ class Menu(models.Model): mid = menu['id'] # new menu are prefixed by new- if isinstance(mid, pycompat.string_types): - new_menu = self.create({'name': menu['name']}) + new_menu = self.create({'name': menu['name'], 'website_id': website_id}) replace_id(mid, new_menu.id) for menu in data['data']: menu_id = self.browse(menu['id']) diff --git a/addons/website/static/src/js/content/menu.js b/addons/website/static/src/js/content/menu.js index 6effb471ec9..3511f51a70f 100644 --- a/addons/website/static/src/js/content/menu.js +++ b/addons/website/static/src/js/content/menu.js @@ -67,7 +67,7 @@ sAnimation.registry.affixMenu = sAnimation.Class.extend({ // Reset opened menus this.$dropdowns.removeClass('show'); - this.$navbarCollapses.removeClass('in').attr('aria-expanded', false); + this.$navbarCollapses.removeClass('show').attr('aria-expanded', false); }, }); diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js index 0f3fff53dfc..a51c8011e92 100644 --- a/addons/website/static/src/js/content/website_root.js +++ b/addons/website/static/src/js/content/website_root.js @@ -287,7 +287,7 @@ var WebsiteRoot = BodyManager.extend({ * @param {OdooEvent} ev */ _multiWebsiteSwitch: function (ev) { - var website_id_to_switch_to = ev.target.getAttribute('website-id'); + var website_id_to_switch_to = ev.currentTarget.getAttribute('website-id'); this._rpc({ route: '/website/force_website', params: { diff --git a/addons/website/static/src/js/menu/content.js b/addons/website/static/src/js/menu/content.js index bcb46553b65..91494a4018b 100644 --- a/addons/website/static/src/js/menu/content.js +++ b/addons/website/static/src/js/menu/content.js @@ -542,7 +542,7 @@ var EditMenuDialog = weWidgets.Dialog.extend({ this._rpc({ model: 'website.menu', method: 'save', - args: [[context.website_id], { data: data, to_delete: self.to_delete }], + args: [context.website_id, { data: data, to_delete: self.to_delete }], context: context, }).then(function () { return _super(); diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js index 50434604677..ce73fd35d3d 100644 --- a/addons/website/static/src/js/widgets/theme.js +++ b/addons/website/static/src/js/widgets/theme.js @@ -55,7 +55,6 @@ var ThemeCustomizeDialog = Dialog.extend({ */ start: function () { var self = this; - this._generateDialogHTML(); this.$modal.addClass('o_theme_customize_modal'); @@ -77,7 +76,7 @@ var ThemeCustomizeDialog = Dialog.extend({ this._loadViews() ); }, - + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- diff --git a/addons/website/static/src/scss/website.ui.scss b/addons/website/static/src/scss/website.ui.scss index a7be7e98290..f146b3de677 100644 --- a/addons/website/static/src/scss/website.ui.scss +++ b/addons/website/static/src/scss/website.ui.scss @@ -38,7 +38,7 @@ body.o_connected_user { } } .o_menu_systray { - a { + > li > a { padding: 0 $grid-gutter-width/2; &.css_edit_dynamic{ diff --git a/addons/website/static/src/xml/website.backend.xml b/addons/website/static/src/xml/website.backend.xml index 6bdb10f24c6..5508d4be9d9 100644 --- a/addons/website/static/src/xml/website.backend.xml +++ b/addons/website/static/src/xml/website.backend.xml @@ -70,9 +70,9 @@ -
+
- diff --git a/addons/website/static/src/xml/website.pageProperties.xml b/addons/website/static/src/xml/website.pageProperties.xml index 7ac590cda2f..cf4719d5951 100644 --- a/addons/website/static/src/xml/website.pageProperties.xml +++ b/addons/website/static/src/xml/website.pageProperties.xml @@ -45,7 +45,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/addons/website/tests/test_menu.py b/addons/website/tests/test_menu.py index e01b020bef8..8ceab5b6bf5 100644 --- a/addons/website/tests/test_menu.py +++ b/addons/website/tests/test_menu.py @@ -3,10 +3,19 @@ from odoo.tests import common class TestMenu(common.TransactionCase): - def setUp(self): - super(TestMenu, self).setUp() + + def test_website_own_menu_container(self): + Website = self.env['website'] Menu = self.env['website.menu'] + total_menus = Menu.search_count([]) + Website.create({'name': 'new website'}) + self.assertEqual(total_menus + 2, Menu.search_count([]), "New website's bootstraping should have created a container menu and home menu") + + def test_menu_got_duplicated(self): + Menu = self.env['website.menu'] + total_menu_items = Menu.search_count([]) + self.menu_root = Menu.create({ 'name': 'Root', }) @@ -16,95 +25,27 @@ class TestMenu(common.TransactionCase): 'parent_id': self.menu_root.id, }) - self.menu_leaf1 = Menu.create({ - 'name': 'Leaf 1', - 'parent_id': self.menu_child.id, - }) + self.assertEqual(total_menu_items + 4, Menu.search_count([]), "Creating a menu without a website_id should create this menu for every website_id") - self.menu_leaf2 = Menu.create({ - 'name': 'Leaf 2', - 'parent_id': self.menu_root.id, - }) - - # create an extra website so we have 3 in total - self.website_test = self.env['website'].create({'domain': 'test'}) - - def test_cow_leaf(self): - Menu = self.env['website.menu'] - - # backend write, no COW - total_menu_items = Menu.search_count([]) - self.menu_leaf1.write({'name': 'Leaf 1 modified'}) - self.assertEqual(total_menu_items, Menu.search_count([])) - - # write through website 1 - self.menu_leaf1.with_context(website_id=1).write({'name': 'Leaf 1 Website 1'}) - self.assertEqual(self.menu_leaf1.name, 'Leaf 1 modified') - self.assertEqual(total_menu_items + 1, Menu.search_count([])) - new_menu = Menu.search([('name', '=', 'Leaf 1 Website 1')]) - self.assertEqual(new_menu.website_id.id, 1) - - def test_cow_root(self): - Menu = self.env['website.menu'] - - # backend write, no COW - total_menu_items = Menu.search_count([]) - self.menu_root.write({'name': 'Root 1 modified'}) - self.assertEqual(total_menu_items, Menu.search_count([])) - - # write through website 1 - self.menu_root.with_context(website_id=1).write({'name': 'Root Website 1'}) - self.assertEqual(self.menu_root.name, 'Root 1 modified') - self.assertEqual(total_menu_items + 4, Menu.search_count([])) - - # verify parent/child structure - website_specific_leaf2 = Menu.search([('name', '=', 'Leaf 2'), ('website_id', '=', 1)]) - website_specific_leaf1 = Menu.search([('name', '=', 'Leaf 1'), ('website_id', '=', 1)]) - website_specific_child = Menu.search([('name', '=', 'Child'), ('website_id', '=', 1)]) - website_specific_root = Menu.search([('name', '=', 'Root Website 1'), ('website_id', '=', 1)]) - self.assertEqual(website_specific_leaf2.parent_id.id, website_specific_root.id) - self.assertEqual(website_specific_leaf1.parent_id.id, website_specific_child.id) - self.assertEqual(website_specific_child.parent_id.id, website_specific_root.id) - - def test_cou_leaf(self): - Menu = self.env['website.menu'] - - # backend unlink, no COU - self.menu_leaf1.unlink() - self.assertEqual(len(Menu.search([('name', '=', 'Leaf 1')])), 0) - - total_menu_items = Menu.search_count([]) - self.menu_leaf2.with_context(website_id=1).unlink() - - website_specific_menus = Menu.search([('name', '=', 'Leaf 2')]) - self.assertEqual(len(website_specific_menus), 2) - self.assertEqual(set(website_specific_menus.mapped('website_id').ids), set([2, self.website_test.id])) - - # exactly 2 website-specific menu items were created and one was deleted - self.assertEqual(total_menu_items + 1, Menu.search_count([])) - - def test_cou_root_backend(self): + def test_menu_count(self): Menu = self.env['website.menu'] total_menu_items = Menu.search_count([]) - self.menu_root.unlink() - # should have deleted root and all children - self.assertEqual(total_menu_items - 4, Menu.search_count([])) + top_menu = self.env['website'].get_current_website().menu_id + data = [ + { + 'id': 'new-1', + 'parent_id': top_menu.id, + 'name': 'New Menu Specific 1', + 'url': '/new-specific-1', + }, + { + 'id': 'new-2', + 'parent_id': top_menu.id, + 'name': 'New Menu Specific 2', + 'url': '/new-specific-2', + } + ] + Menu.save(1, {'data': data, 'to_delete': []}) - def test_cou_root_frontend(self): - Menu = self.env['website.menu'] - total_menu_items = Menu.search_count([]) - self.menu_root.with_context(website_id=1).unlink() - - # should have removed 4 generic menus and created root and all children for 2 remaining websites - self.assertEqual(Menu.search_count([('name', '=', 'Root'), ('website_id', '=', 2)]), 1) - self.assertEqual(Menu.search_count([('name', '=', 'Child'), ('website_id', '=', 2)]), 1) - self.assertEqual(Menu.search_count([('name', '=', 'Leaf 1'), ('website_id', '=', 2)]), 1) - self.assertEqual(Menu.search_count([('name', '=', 'Leaf 2'), ('website_id', '=', 2)]), 1) - - self.assertEqual(Menu.search_count([('name', '=', 'Root'), ('website_id', '=', self.website_test.id)]), 1) - self.assertEqual(Menu.search_count([('name', '=', 'Child'), ('website_id', '=', self.website_test.id)]), 1) - self.assertEqual(Menu.search_count([('name', '=', 'Leaf 1'), ('website_id', '=', self.website_test.id)]), 1) - self.assertEqual(Menu.search_count([('name', '=', 'Leaf 2'), ('website_id', '=', self.website_test.id)]), 1) - - self.assertEqual(total_menu_items + 4, Menu.search_count([])) + self.assertEqual(total_menu_items + 2, Menu.search_count([]), "Creating 2 new menus should create only 2 menus records") diff --git a/addons/website/tests/test_page.py b/addons/website/tests/test_page.py index 76bfa52e51d..3f717740e79 100644 --- a/addons/website/tests/test_page.py +++ b/addons/website/tests/test_page.py @@ -1,5 +1,5 @@ # coding: utf-8 -from odoo.tests import common +from odoo.tests import common, HttpCase class TestPage(common.TransactionCase): @@ -31,8 +31,58 @@ class TestPage(common.TransactionCase): self.page_1_menu = Menu.create({ 'name': 'Page 1 menu', 'page_id': self.page_1.id, + 'website_id': 1, }) + def test_copy_page(self): + View = self.env['ir.ui.view'] + Page = self.env['website.page'] + Menu = self.env['website.menu'] + # Specific page + self.specific_view = View.create({ + 'name': 'Base', + 'type': 'qweb', + 'arch': '
Specific View
', + 'key': 'test.specific_view', + }) + self.page_specific = Page.create({ + 'view_id': self.specific_view.id, + 'url': '/page_specific', + 'website_id': 1, + }) + self.page_specific_menu = Menu.create({ + 'name': 'Page Specific menu', + 'page_id': self.page_specific.id, + 'website_id': 1, + }) + total_pages = Page.search_count([]) + total_menus = Menu.search_count([]) + # Copying a specific page should create a new page with an unique URL (suffixed by -X) + Page.clone_page(self.page_specific.id, True) + cloned_page = Page.search([('url', '=', '/page_specific-1')]) + cloned_menu = Menu.search([('url', '=', '/page_specific-1')]) + self.assertEqual(len(cloned_page), 1, "A page with an URL /page_specific-1 should've been created") + self.assertEqual(Page.search_count([]), total_pages + 1, "Should have cloned the page") + # It should also copy its menu with new url/name/page_id (if the page has a menu) + self.assertEqual(len(cloned_menu), 1, "A specific page (with a menu) being cloned should have it's menu also cloned") + self.assertEqual(cloned_menu.page_id, cloned_page, "The new cloned menu and the new cloned page should be linked (m2o)") + self.assertEqual(Menu.search_count([]), total_menus + 1, "Should have cloned the page menu") + + total_pages = Page.search_count([]) + total_menus = Menu.search_count([]) + + # Copying a generic page should create a specific page with same URL + Page.clone_page(self.page_1.id, True) + cloned_generic_page = Page.search([('url', '=', '/page_1'), ('id', '!=', self.page_1.id), ('website_id', '!=', False)]) + self.assertEqual(len(cloned_generic_page), 1, "A generic page being cloned should create a specific one for the current website") + self.assertEqual(cloned_generic_page.url, self.page_1.url, "The URL of the cloned specific page should be the same as the generic page it has been cloned from") + self.assertEqual(Page.search_count([]), total_pages + 1, "Should have cloned the generic page as a specific page for this website") + self.assertEqual(Menu.search_count([]), total_menus, "It should not create a new menu as the generic page's menu belong to another website") + # Except if the URL already exists for this website (its the case now that we already cloned it once) + Page.clone_page(self.page_1.id, True) + cloned_generic_page_2 = Page.search([('url', '=', '/page_1-1'), ('id', '!=', self.page_1.id)]) + self.assertEqual(len(cloned_generic_page_2), 1, "A generic page being cloned should create a specific page with a new URL if there is already a specific page with that URL") + def test_cow_page(self): Menu = self.env['website.menu'] Page = self.env['website.page'] @@ -50,27 +100,23 @@ class TestPage(common.TransactionCase): # edit through frontend self.page_1.with_context(website_id=1).write({'arch': '
website 1 content
'}) - # should have created website-specific copies for: - # - page - # - menu - # - view x2 (base view + extension view) - # and shouldn't have touched original records + # 1. should have created website-specific copies for: + # - page + # - view x2 (base view + extension view) + # 2. should not have created menu copy as menus are not shared/COW + # 3. and shouldn't have touched original records self.assertEqual(total_pages + 1, Page.search_count([])) - self.assertEqual(total_menus + 1, Menu.search_count([])) + self.assertEqual(total_menus, Menu.search_count([])) self.assertEqual(total_views + 2, View.search_count([])) self.assertEqual(self.page_1.arch, '
modified base content
') self.assertEqual(bool(self.page_1.website_id), False) - self.assertEqual(bool(self.page_1_menu.website_id), False) new_page = Page.search([('url', '=', '/page_1'), ('id', '!=', self.page_1.id)]) self.assertEqual(new_page.website_id.id, 1) self.assertEqual(new_page.view_id.inherit_children_ids[0].website_id.id, 1) self.assertEqual(new_page.arch, '
website 1 content
') - new_menu = new_page.menu_ids[0] - self.assertEqual(new_menu.website_id.id, 1) - def test_cow_extension_view(self): ''' test cow on extension view itself (like web_editor would do in the frontend) ''' Menu = self.env['website.menu'] @@ -98,14 +144,12 @@ class TestPage(common.TransactionCase): self.assertEqual(self.extension_view.arch, '
modified extension content
') self.assertEqual(bool(self.page_1.website_id), False) - self.assertEqual(bool(self.page_1_menu.website_id), False) new_view = View.search([('name', '=', 'Extension'), ('website_id', '=', 1)]) self.assertEqual(new_view.arch, '
website 1 content
') self.assertEqual(new_view.website_id.id, 1) def test_cou_page_backend(self): - Menu = self.env['website.menu'] Page = self.env['website.page'] View = self.env['ir.ui.view'] @@ -113,12 +157,10 @@ class TestPage(common.TransactionCase): self.extension_view.unlink() self.page_1.unlink() - self.assertEqual(Menu.search_count([('name', '=', 'Page 1 menu')]), 0) self.assertEqual(Page.search_count([('url', '=', '/page_1')]), 0) self.assertEqual(View.search_count([('name', 'in', ('Base', 'Extension'))]), 0) def test_cou_page_frontend(self): - Menu = self.env['website.menu'] Page = self.env['website.page'] View = self.env['ir.ui.view'] @@ -129,8 +171,40 @@ class TestPage(common.TransactionCase): self.assertEqual(bool(self.base_view.exists()), False) self.assertEqual(bool(self.page_1.exists()), False) + # Not COU but deleting a page will delete its menu (cascade) self.assertEqual(bool(self.page_1_menu.exists()), False) - self.assertEqual(Menu.search([('name', '=', 'Page 1 menu')]).website_id.id, 2) self.assertEqual(Page.search([('url', '=', '/page_1')]).website_id.id, 2) self.assertEqual(View.search([('name', 'in', ('Base', 'Extension'))]).mapped('website_id').id, 2) + + +class Crawler(HttpCase): + def test_unpublished_page(self): + Page = self.env['website.page'] + View = self.env['ir.ui.view'] + base_view = View.create({ + 'name': 'Base', + 'type': 'qweb', + 'arch': ''' + + I am a generic page + + ''', + 'key': 'test.base_view', + }) + generic_page = Page.create({ + 'view_id': base_view.id, + 'url': '/page_1', + 'website_published': True, + }) + + specific_page = generic_page.copy({'website_id': self.env['website'].get_current_website().id}) + specific_page.write({'website_published': False, 'arch': generic_page.arch.replace('I am a generic page', 'I am a specific page')}) + + r = self.url_open(specific_page.url) + self.assertEqual(r.status_code, 404, "Restricted users should see a 404 and not the generic one as we unpublished the specific one") + + self.authenticate('admin', 'admin') + r = self.url_open(specific_page.url) + self.assertEqual(r.status_code, 200, "Admin should see the specific unpublished page") + self.assertEqual('I am a specific page' in r.text, True, "Admin should see the specific unpublished page") diff --git a/addons/website/views/res_config_settings_views.xml b/addons/website/views/res_config_settings_views.xml index d8b44e6c65f..597895debcc 100644 --- a/addons/website/views/res_config_settings_views.xml +++ b/addons/website/views/res_config_settings_views.xml @@ -330,7 +330,7 @@ id="menu_website_websites_list" action="action_website_list" parent="menu_website_global_configuration" - groups="website.group_multi_website" + groups="base.group_no_one" sequence="10" /> diff --git a/addons/website/views/website_navbar_templates.xml b/addons/website/views/website_navbar_templates.xml index 7f0adea2f56..f4e2d7c24dc 100644 --- a/addons/website/views/website_navbar_templates.xml +++ b/addons/website/views/website_navbar_templates.xml @@ -77,20 +77,21 @@
  • -
    +
  • New diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 5ff55756fc9..b6dc1f51f92 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -210,7 +210,7 @@ website.user_id != user_id - + diff --git a/addons/website/views/website_views.xml b/addons/website/views/website_views.xml index 050110af288..c0e1b4c842c 100644 --- a/addons/website/views/website_views.xml +++ b/addons/website/views/website_views.xml @@ -159,8 +159,9 @@ + - + diff --git a/addons/website_blog/data/website_blog_data.xml b/addons/website_blog/data/website_blog_data.xml index 142820f109b..d4826083dd8 100644 --- a/addons/website_blog/data/website_blog_data.xml +++ b/addons/website_blog/data/website_blog_data.xml @@ -10,7 +10,6 @@ 40 - diff --git a/addons/website_blog/views/website_blog_views.xml b/addons/website_blog/views/website_blog_views.xml index d4fe3bcfc57..ddeee669d33 100644 --- a/addons/website_blog/views/website_blog_views.xml +++ b/addons/website_blog/views/website_blog_views.xml @@ -73,6 +73,7 @@ + diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py index f82baa95456..5bb045a320f 100644 --- a/addons/website_event/models/event.py +++ b/addons/website_event/models/event.py @@ -84,7 +84,7 @@ class Event(models.Model): event.menu_id.unlink() elif event.website_menu: if not event.menu_id: - root_menu = self.env['website.menu'].create({'name': event.name}) + root_menu = self.env['website.menu'].create({'name': event.name, 'website_id': event.id}) event.menu_id = root_menu for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()): event._create_menu(sequence, name, url, xml_id) @@ -99,6 +99,7 @@ class Event(models.Model): 'url': url, 'parent_id': self.menu_id.id, 'sequence': sequence, + 'website_id': self.id, }) return menu diff --git a/addons/website_mass_mailing/views/snippets_templates.xml b/addons/website_mass_mailing/views/snippets_templates.xml index 6a2413e9d20..5f991f7bfcb 100644 --- a/addons/website_mass_mailing/views/snippets_templates.xml +++ b/addons/website_mass_mailing/views/snippets_templates.xml @@ -29,7 +29,7 @@