diff --git a/addons/test_website/__manifest__.py b/addons/test_website/__manifest__.py
index 01eeed34968..290caa3fbc0 100644
--- a/addons/test_website/__manifest__.py
+++ b/addons/test_website/__manifest__.py
@@ -17,8 +17,12 @@ models which only purpose is to run tests.""",
'website',
'theme_default',
],
+ 'demo': [
+ 'data/test_website_demo.xml',
+ ],
'data': [
'views/templates.xml',
+ 'views/test_model_views.xml',
'data/test_website_data.xml',
'security/ir.model.access.csv',
],
diff --git a/addons/test_website/data/test_website_data.xml b/addons/test_website/data/test_website_data.xml
index 3217fcf42f1..8fe80c15e40 100644
--- a/addons/test_website/data/test_website_data.xml
+++ b/addons/test_website/data/test_website_data.xml
@@ -10,6 +10,14 @@
+
+
+ Test Model Generic
+
+
+ Test Model Website 1
+
+
diff --git a/addons/test_website/data/test_website_demo.xml b/addons/test_website/data/test_website_demo.xml
new file mode 100644
index 00000000000..73e6a1d0287
--- /dev/null
+++ b/addons/test_website/data/test_website_demo.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ Test Model Website 2
+
+
+
+
diff --git a/addons/test_website/models/model.py b/addons/test_website/models/model.py
index 3c7150f6448..2cd157a37a1 100644
--- a/addons/test_website/models/model.py
+++ b/addons/test_website/models/model.py
@@ -10,12 +10,17 @@ class TestModel(models.Model):
_name = 'test.model'
_inherit = [
'website.seo.metadata',
- 'website.published.mixin',
+ 'website.published.multi.mixin',
'website.searchable.mixin',
]
_description = 'Website Model Test'
name = fields.Char(required=True)
+ # `cascade` is needed as there is demo data for this model which are bound
+ # to website 2 (demo website). But some tests are unlinking the website 2,
+ # which would fail if the `cascade` is not set. Note that the website 2 is
+ # never set on any records in all other modules.
+ website_id = fields.Many2one('website', string='Website', ondelete='cascade')
@api.model
def _search_get_detail(self, website, order, options):
diff --git a/addons/test_website/static/tests/tours/page_manager.js b/addons/test_website/static/tests/tours/page_manager.js
new file mode 100644
index 00000000000..50d7856a05e
--- /dev/null
+++ b/addons/test_website/static/tests/tours/page_manager.js
@@ -0,0 +1,70 @@
+/** @odoo-module **/
+
+import { registry } from "@web/core/registry";
+
+registry.category("web_tour.tours").add('test_website_page_manager', {
+ test: true,
+ url: '/web#action=test_website.action_test_model',
+ steps: () => [
+// Part 1: check that the website filter is working
+{
+ content: "Check that we see records from My Website",
+ trigger: ".o_list_table .o_data_row .o_data_cell[name=name]:contains('Test Model Website 1') " +
+ "~ .o_data_cell[name=website_id]:contains('My Website')",
+ run: () => null, // it's a check
+}, {
+ content: "Check that there is only 2 records in the pager",
+ trigger: ".o_pager .o_pager_value:contains('1-2')",
+ run: () => null, // it's a check
+}, {
+ content: "Click on the 'Select all records' checkbox",
+ trigger: "thead .o_list_record_selector",
+}, {
+ content: "Check that there is only 2 records selected",
+ trigger: ".o_list_selection_box:contains('2 selected')",
+ run: () => null, // it's a check
+}, {
+ content: "Click on the 'Select all records' checkbox again to unselect all records and see the search bar",
+ trigger: "thead .o_list_record_selector",
+}, {
+ content: "Click on the search options",
+ trigger: ".o_searchview_dropdown_toggler",
+}, {
+ content: "Select My Website 2",
+ trigger: ".o_dropdown_container.o_website_menu > .dropdown-item:contains('My Website 2')",
+}, {
+ // This step is just here to ensure there is more records than the 2
+ // available on website 1, to ensure the test is actually testing something.
+ content: "Check that we see records from My Website 2",
+ trigger: ".o_list_table .o_data_row .o_data_cell[name=name]:contains('Test Model Website 2') " +
+ "~ .o_data_cell[name=website_id]:contains('My Website 2')",
+ run: () => null, // it's a check
+},
+// Part 2: ensure Kanban View is working / not crashing
+{
+ content: "Click on Kanban View",
+ trigger: '.o_cp_switch_buttons .o_kanban',
+}, {
+ content: "Click on List View",
+ extra_trigger: '.o_kanban_renderer',
+ trigger: '.o_cp_switch_buttons .o_list',
+}, {
+ content: "Wait for List View to be loaded",
+ trigger: '.o_list_renderer',
+ run: () => null, // it's a check
+}]
+});
+
+registry.category("web_tour.tours").add('test_website_page_manager_js_class_bug', {
+ test: true,
+ url: '/web#action=test_website.action_test_model_js_class_bug',
+ steps: () => [
+{
+ content: "Click on Kanban View",
+ trigger: '.o_cp_switch_buttons .o_kanban',
+}, {
+ content: "Wait for Kanban View to be loaded",
+ trigger: '.o_kanban_renderer',
+ run: () => null, // it's a check
+}]
+});
diff --git a/addons/test_website/tests/__init__.py b/addons/test_website/tests/__init__.py
index 977fcd95c7c..58eb63a6e22 100644
--- a/addons/test_website/tests/__init__.py
+++ b/addons/test_website/tests/__init__.py
@@ -9,6 +9,7 @@ from . import test_image_upload_progress
from . import test_is_multilang
from . import test_media
from . import test_multi_company
+from . import test_page_manager
from . import test_page
from . import test_performance
from . import test_qweb
diff --git a/addons/test_website/tests/test_page_manager.py b/addons/test_website/tests/test_page_manager.py
new file mode 100644
index 00000000000..80d460f0924
--- /dev/null
+++ b/addons/test_website/tests/test_page_manager.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import odoo.tests
+
+
+@odoo.tests.common.tagged('post_install', '-at_install')
+class TestWebsitePageManager(odoo.tests.HttpCase):
+ def test_page_manager_test_model(self):
+ if self.env['website'].search_count([]) == 1:
+ website2 = self.env['website'].create({
+ 'name': 'My Website 2',
+ 'domain': '',
+ 'sequence': 20,
+ })
+ else:
+ website2 = self.env['website'].search([], order='id desc', limit=1)
+ self.env['test.model'].create({'name': 'Test Model Website 2', 'website_id': website2.id})
+ self.assertTrue(
+ len(set([t.website_id.id for t in self.env['test.model'].search([])])) >= 3,
+ "There should at least be one record without website_id and one for 2 different websites",
+ )
+ self.start_tour('/web#action=test_website.action_test_model', 'test_website_page_manager', login="admin")
+ # This second test is about ensuring that you can switch from a list
+ # view which has no `website_pages_list` js_class to its kanban view
+ self.start_tour('/web#action=test_website.action_test_model_js_class_bug', 'test_website_page_manager_js_class_bug', login="admin")
diff --git a/addons/test_website/views/test_model_views.xml b/addons/test_website/views/test_model_views.xml
new file mode 100644
index 00000000000..0b3bebe3d2d
--- /dev/null
+++ b/addons/test_website/views/test_model_views.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+ test.model.kanban
+ test.model
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Published
+ Not Published
+
+
+
+
+
+
+
+
+ Test Model Pages Tree
+ test.model
+ 99
+
+
+
+
+
+
+
+
+
+ Test Model Pages
+ test.model
+ tree,kanban,form
+
+
+
+
+
+ Test Model Pages Tree js_class bug
+ test.model
+ 99
+
+
+
+
+
+
+
+
+
+
+ Test Model Pages js_class bug
+ test.model
+ tree,kanban,form
+
+
+
+
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index e6f1e228f67..20b64f01ca1 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -1367,6 +1367,25 @@ class Website(models.Model):
record['lastmod'] = page['write_date'].date()
yield record
+ def get_website_page_ids(self):
+ if not self.env.user.has_group('website.group_website_restricted_editor'):
+ # Note that `website.pages` have `0,0,0,0` ACL rights by default for
+ # everyone except for the website designer which receive `1,0,0,0`.
+ # So the "Website/Site/Content/Pages" menu to reach the page manager
+ # is not shown to the restricted users, as the action linked model
+ # (website.page) can't be access. It's how the Odoo framework works.
+ # Still, we let the restricted editor access this resource for
+ # custos granting them read and/or write access on page.
+ raise AccessError(_("Access Denied"))
+
+ domain = [('url', '!=', False)]
+ if self:
+ domain = AND([domain, self.website_domain()])
+ pages = self.env['website.page'].sudo().search(domain)
+ if self:
+ pages = pages._get_most_specific_pages()
+ return pages.ids
+
def _get_website_pages(self, domain=None, order='name', limit=None):
if domain is None:
domain = []
diff --git a/addons/website/static/src/components/views/page_kanban.js b/addons/website/static/src/components/views/page_kanban.js
index 1ae9ef607ba..7e346fa60de 100644
--- a/addons/website/static/src/components/views/page_kanban.js
+++ b/addons/website/static/src/components/views/page_kanban.js
@@ -1,6 +1,7 @@
/** @odoo-module **/
import {PageControllerMixin, PageRendererMixin} from "./page_views_mixin";
+import {PageSearchModel} from "./page_search_model";
import {registry} from '@web/core/registry';
import {kanbanView} from "@web/views/kanban/kanban_view";
import {CheckboxItem} from "@web/core/dropdown/checkbox_item";
@@ -19,6 +20,7 @@ PageKanbanController.components = {
CheckboxItem,
};
+// TODO master: remove `PageRendererMixin` extend, props override and template
export class PageKanbanRenderer extends PageRendererMixin(kanbanView.Renderer) {}
PageKanbanRenderer.props = [
...kanbanView.Renderer.props,
@@ -30,6 +32,7 @@ export const PageKanbanView = {
...kanbanView,
Renderer: PageKanbanRenderer,
Controller: PageKanbanController,
+ SearchModel: PageSearchModel,
};
registry.category("views").add("website_pages_kanban", PageKanbanView);
diff --git a/addons/website/static/src/components/views/page_kanban.xml b/addons/website/static/src/components/views/page_kanban.xml
index 258145ce41f..63025ef7092 100644
--- a/addons/website/static/src/components/views/page_kanban.xml
+++ b/addons/website/static/src/components/views/page_kanban.xml
@@ -1,15 +1,19 @@
+
recordFilter(record, props.list.records)
+ recordFilter(groupOrRecord.record, props.list.records)
+
+
state.activeWebsite
diff --git a/addons/website/static/src/components/views/page_list.js b/addons/website/static/src/components/views/page_list.js
index 3ea9a8ec484..71ec551b867 100644
--- a/addons/website/static/src/components/views/page_list.js
+++ b/addons/website/static/src/components/views/page_list.js
@@ -2,6 +2,7 @@
import { _t } from "@web/core/l10n/translation";
import {PageControllerMixin, PageRendererMixin} from "./page_views_mixin";
+import {PageSearchModel} from "./page_search_model";
import {registry} from '@web/core/registry';
import {listView} from '@web/views/list/list_view';
import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog";
@@ -94,6 +95,7 @@ PageListController.components = {
CheckboxItem,
};
+// TODO master: remove `PageRendererMixin` extend and props override
export class PageListRenderer extends PageRendererMixin(listView.Renderer) {}
PageListRenderer.props = [
...listView.Renderer.props,
@@ -105,6 +107,7 @@ export const PageListView = {
...listView,
Renderer: PageListRenderer,
Controller: PageListController,
+ SearchModel: PageSearchModel,
};
registry.category("views").add("website_pages_list", PageListView);
diff --git a/addons/website/static/src/components/views/page_list.xml b/addons/website/static/src/components/views/page_list.xml
index d8d3d36f921..03c820406e0 100644
--- a/addons/website/static/src/components/views/page_list.xml
+++ b/addons/website/static/src/components/views/page_list.xml
@@ -7,12 +7,15 @@
+
recordFilter(record, list.records)
+
+
state.activeWebsite
diff --git a/addons/website/static/src/components/views/page_search_model.js b/addons/website/static/src/components/views/page_search_model.js
new file mode 100644
index 00000000000..ddb03dfa456
--- /dev/null
+++ b/addons/website/static/src/components/views/page_search_model.js
@@ -0,0 +1,90 @@
+/** @odoo-module */
+
+import { useService } from "@web/core/utils/hooks";
+import { Domain } from '@web/core/domain';
+import { SearchModel } from '@web/search/search_model';
+import { onWillStart, useState } from "@odoo/owl";
+
+export class PageSearchModel extends SearchModel {
+ /**
+ * @override
+ */
+ setup() {
+ super.setup(...arguments);
+ this.website = useService('website');
+
+ this.rpc = useService('rpc');
+ this.pagesState = useState({
+ websiteDomain: false,
+ });
+ onWillStart(async () => {
+ // Before the searchModel performs its DB search call, append the
+ // website domain to the search domain.
+ await this.website.fetchWebsites();
+ const website = this.website.currentWebsite || this.website.websites[0];
+ this.notifyWebsiteChange(website.id);
+ });
+ }
+
+ /**
+ * @override
+ */
+ exportState() {
+ const state = super.exportState();
+ state.websiteDomain = this.pagesState.websiteDomain;
+ return state;
+ }
+
+ /**
+ * @override
+ */
+ _importState(state) {
+ super._importState(...arguments);
+
+ if (state.websiteDomain) {
+ this.pagesState.websiteDomain = state.websiteDomain;
+ }
+ }
+
+ /**
+ * @override
+ */
+ _getDomain(params = {}) {
+ let domain = super._getDomain(params);
+ if (!this.pagesState.websiteDomain) {
+ return domain;
+ }
+
+ domain = Domain.and([
+ domain,
+ this.pagesState.websiteDomain,
+ ]);
+ return params.raw ? domain : domain.toList();
+ }
+
+ /**
+ * Updates the website domain state and notifies the change. That domain
+ * state will be appended to the base SearchModel domain.
+ *
+ * @param {number} websiteId - The ID of the website.
+ */
+ async notifyWebsiteChange(websiteId) {
+ let websiteDomain = [];
+ if (websiteId) {
+ if (this.resModel === 'website.page') {
+ // In case of `website.page`, we can't find the website pages
+ // with a regular domain (because we need to filter duplicates).
+ const pageIds = await this.orm.call(
+ "website",
+ "get_website_page_ids",
+ [websiteId],
+ );
+ websiteDomain = [['id', 'in', pageIds]];
+ } else {
+ websiteDomain = [['website_id', 'in', [false, websiteId]]];
+ }
+ }
+ this.pagesState.websiteDomain = websiteDomain;
+ this._notify();
+ }
+}
diff --git a/addons/website/static/src/components/views/page_views_mixin.js b/addons/website/static/src/components/views/page_views_mixin.js
index efac083c490..cfcda5cce9d 100644
--- a/addons/website/static/src/components/views/page_views_mixin.js
+++ b/addons/website/static/src/components/views/page_views_mixin.js
@@ -75,9 +75,11 @@ export const PageControllerMixin = (component) => class extends component {
onSelectWebsite(website) {
this.state.activeWebsite = website;
+ this.env.searchModel.notifyWebsiteChange(website.id);
}
};
+// TODO: Remove in master, records are not hidden through `t-if` anymore.
export const PageRendererMixin = (component) => class extends component {
/**
* The goal here is to tweak the renderer to display records following some
diff --git a/addons/website/views/website_pages_views.xml b/addons/website/views/website_pages_views.xml
index 31e6237496f..5dfc91b049d 100644
--- a/addons/website/views/website_pages_views.xml
+++ b/addons/website/views/website_pages_views.xml
@@ -79,7 +79,8 @@
-
diff --git a/addons/website_forum/views/forum_post_views.xml b/addons/website_forum/views/forum_post_views.xml
index ccfcb052b89..98b26cf56ec 100644
--- a/addons/website_forum/views/forum_post_views.xml
+++ b/addons/website_forum/views/forum_post_views.xml
@@ -110,7 +110,7 @@
forum.post99
-
+