diff --git a/addons/website/models/mixins.py b/addons/website/models/mixins.py index 93786227252..ab64ea80d7d 100644 --- a/addons/website/models/mixins.py +++ b/addons/website/models/mixins.py @@ -284,8 +284,10 @@ class WebsitePublishedMultiMixin(WebsitePublishedMixin): def open_website_url(self): client_action_url = self.env['website'].get_client_action_url(self.website_url) - if self.website_id.domain: - client_action_url = url_join(self.website_id.domain, f'{client_action_url}&website_id={self.website_id.id}') + if self.website_id: + client_action_url = f'{client_action_url}&website_id={self.website_id.id}' + if self.website_id.domain: + client_action_url = url_join(self.website_id.domain, client_action_url) return { 'type': 'ir.actions.act_url', 'url': client_action_url, diff --git a/addons/website/models/website_page.py b/addons/website/models/website_page.py index f742e24775d..e789119974e 100644 --- a/addons/website/models/website_page.py +++ b/addons/website/models/website_page.py @@ -83,6 +83,13 @@ class Page(models.Model): # If the page is no longer in menu, we should remove its website_menu page.menu_ids.unlink() + # This update was added to make sure the mixin calculations are correct + # (page.website_url > page.url). + @api.depends('url') + def _compute_website_url(self): + for page in self: + page.website_url = page.url + def _get_most_specific_pages(self): ''' Returns the most specific pages in self. ''' ids = [] @@ -277,6 +284,15 @@ class Page(models.Model): url = '/' + slugify(page_url, max_length=1024, path=True) return self.env['website'].with_context(website_id=website_id).get_unique_path(url) + def action_page_debug_view(self): + return { + 'type': 'ir.actions.act_window', + 'res_model': 'ir.ui.view', + 'res_id': self.view_id.id, + 'view_mode': 'form', + 'view_id': self.env.ref('website.view_view_form_extend').id, + } + # this is just a dummy function to be used as ormcache key def _cached_response(): diff --git a/addons/website/static/src/components/dialog/page_properties.js b/addons/website/static/src/components/dialog/page_properties.js index 3011d23013a..28bf4437a84 100644 --- a/addons/website/static/src/components/dialog/page_properties.js +++ b/addons/website/static/src/components/dialog/page_properties.js @@ -7,7 +7,7 @@ import {WebsiteDialog} from './dialog'; import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog"; import {qweb, _t} from 'web.core'; -const {Component, onWillStart, onRendered, useState, xml, useRef, markup} = owl; +const {Component, onWillStart, useState, xml, useRef, markup} = owl; export class PageDependencies extends Component { setup() { @@ -209,54 +209,3 @@ PagePropertiesDialog.defaultProps = { }, onClose: () => {}, }; - -// TODO remove this once the 'website.page' listView is adapted to OWL. -export class PagePropertiesDialogManager extends Component { - setup() { - this.dialog = useService('dialog'); - onRendered(this.createDialog); - } - - createDialog() { - switch (this.props.mode) { - case 'clone': - this.dialog.add(DuplicatePageDialog, { - pageId: this.props.resId, - onDuplicate: this.props.onClose, - }); - break; - case 'delete': - this.dialog.add(DeletePageDialog, { - pageId: this.props.resId, - onDelete: this.props.onClose, - }); - break; - default: - this.dialog.add(PagePropertiesDialog, { - resId: this.props.resId, - onClose: this.props.onClose, - onRecordSaved: this.props.onRecordSaved, - }); - break; - } - } -} -PagePropertiesDialogManager.template = xml``; -PagePropertiesDialogManager.props = { - onClose: { - type: Function, - optional: true, - }, - mode: { - type: String, - optional: true, - }, - resId: { - type: [Number, Boolean], - optional: true, - }, - onRecordSaved: { - type: Function, - optional: true, - } -}; diff --git a/addons/website/static/src/components/fields/fields.js b/addons/website/static/src/components/fields/fields.js index 35aae70d298..0905895f91e 100644 --- a/addons/website/static/src/components/fields/fields.js +++ b/addons/website/static/src/components/fields/fields.js @@ -5,8 +5,6 @@ import {standardFieldProps} from '@web/views/fields/standard_field_props'; import {useInputField} from '@web/views/fields/input_field_hook'; import {useService} from '@web/core/utils/hooks'; import {Switch} from '@website/components/switch/switch'; -import AbstractFieldOwl from 'web.AbstractFieldOwl'; -import fieldRegistry from 'web.field_registry_owl'; import {registry} from '@web/core/registry'; import {formatChar} from '@web/views/fields/formatters'; @@ -114,28 +112,6 @@ PageNameField.supportedTypes = ['char']; registry.category("fields").add("page_name", PageNameField); -/** - * Displays 'char' field's value prefixed by a FA icon. - * The prefix is shown by default, but the visibility can be updated depending on - * other field value. - * e.g. `` renders the icon only when 'is_locked' is True. - */ -class FieldFaPrefix extends AbstractFieldOwl { - get prefix() { - const {icon, visibility, title} = this.nodeOptions; - return { - class: icon.split(' ').filter(str => str.indexOf('fa-') === 0).join(' '), - visible: !visibility || !!this.recordData[visibility], - help: title || '', - }; - } -} -FieldFaPrefix.supportedFieldTypes = ['char']; -FieldFaPrefix.template = 'website.FieldFaPrefix'; - -fieldRegistry.add('fa_prefix', FieldFaPrefix); - /** * Displays 'Selection' field's values as images to select. * Image src for each value can be added using the option 'images' on field XML. diff --git a/addons/website/static/src/components/fields/fields.xml b/addons/website/static/src/components/fields/fields.xml index 2364ade9278..eec1073bb0c 100644 --- a/addons/website/static/src/components/fields/fields.xml +++ b/addons/website/static/src/components/fields/fields.xml @@ -62,10 +62,6 @@ - - - -
diff --git a/addons/website/static/src/components/views/page_list.js b/addons/website/static/src/components/views/page_list.js index fe6917063e9..656841201dd 100644 --- a/addons/website/static/src/components/views/page_list.js +++ b/addons/website/static/src/components/views/page_list.js @@ -1,158 +1,109 @@ /** @odoo-module **/ -import ListController from 'web.ListController'; -import viewRegistry from 'web.view_registry'; -import ListView from 'web.ListView'; -import {ComponentWrapper} from 'web.OwlCompatibility'; -import {PagePropertiesDialogManager} from '@website/components/dialog/page_properties'; -import {qweb} from 'web.core'; +import {AddPageDialog} from "../dialog/dialog"; +import {registry} from '@web/core/registry'; +import {listView} from '@web/views/list/list_view'; +import {useService} from "@web/core/utils/hooks"; +import { csrf_token } from 'web.core'; -const WebsitePageListController = ListController.extend({ +const {onWillStart, useState} = owl; - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- +export class PageListController extends listView.Controller { /** * @override */ - willStart() { - return Promise.all([this._super(...arguments), this._getRenderContext()]); - }, - /** - * @override - */ - renderButtons($node) { - this.$buttons = $(qweb.render('PageListView.WebsiteSelect', { - websites: this.websites, - active: this._getWebsite(0), - })); - this.$buttons.find('.dropdown-item').click(this._onButtonClick.bind(this)); - if ($node) { - this.$buttons.appendTo($node); - } - }, + setup() { + super.setup(); + this.website = useService('website'); + this.http = useService('http'); - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + this.websiteSelection = [{id: 0, name: this.env._t("All Websites")}]; - /** - * @override - */ - async _callButtonAction(attrs, record) { - switch (attrs.name) { - case 'action_optimize_seo': - this._goToPage(record.data.url, record.data.website_id.res_id, { - enable_seo: true, - }); - break; - case 'action_manage_page': - await this._addDialog(record); - break; - case 'action_clone_page': - await this._addDialog(record, 'clone'); - break; - case 'action_delete_page': - await this._addDialog(record, 'delete'); - break; - case 'action_edit_page': - this.do_action({ - type: 'ir.actions.act_window', - res_model: 'ir.ui.view', - res_id: record.data.view_id.res_id, - views: [[false, 'form']], - }); - break; - default: - return this._super(...arguments); - } - }, - /** - * @private - */ - _goToPage(path, website, options = {}) { - this.do_action('website.website_preview', { - additional_context: { - params: { - path: path, - website_id: website || '', - ...options, - } - }, + this.state = useState({ + activeWebsite: this.websiteSelection[0], }); - }, - /** - * @private - */ - async _addDialog(record, mode = '') { - this._pagePropertiesDialog = new ComponentWrapper(this, PagePropertiesDialogManager, { - resId: record.data.id, - onClose: this._onCloseDialog.bind(this), - onRecordSaved: this.reload.bind(this), - mode: mode, - }); - await this._pagePropertiesDialog.mount(this.el); - }, - /** - * Removes page properties wrapper when dialog is closed. - * - * @private - */ - _onCloseDialog() { - if (this._pagePropertiesDialog) { - this._pagePropertiesDialog.destroy(); - } - this._pagePropertiesDialog = undefined; - }, - /** - * @private - */ - _getRenderContext() { - return this._rpc({ - model: 'website', - method: 'search_read', - fields: ['id', 'name'], - }).then((result) => { - this.websites = [{id: 0, name: 'All Websites'}, ...result]; - }); - }, - /** - * @private - */ - _getWebsite(id) { - return this.websites.find(website => website.id === id); - }, - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @override - */ - _onOpenRecord(event) { - const record = this.model.get(event.data.id, {raw: true}); - this._goToPage(record.data.url, record.data.website_id); - }, - _onButtonClick: async function (event) { - const activeWebsite = this._getWebsite(parseInt(event.target.dataset.websiteId)); - this.$buttons[0].querySelector('.o_website_active').textContent = activeWebsite.name; - this.reload({ - domain: activeWebsite.id ? [['website_id', '=', activeWebsite.id]] : [], + onWillStart(async () => { + await this.website.fetchWebsites(); + this.websiteSelection.push(...this.website.websites); }); } -}); -const WebsitePageListView = ListView.extend({ - config: _.extend({}, ListView.prototype.config, { - Controller: WebsitePageListController, - }), -}); + /** + * @override + */ + onClickCreate() { + if (this.props.resModel === 'website.page') { + return this.dialogService.add(AddPageDialog, { + addPage: async (name, addMenu) => { + // TODO this is duplicated code from new_content.js, this + // should be shared somehow. + // FIXME this always create on website 1 whatever we do. + const url = `/website/add/${encodeURIComponent(name)}`; + const data = await this.http.post(url, { 'add_menu': addMenu || '', csrf_token }); + if (data.view_id) { + this.actionService.doAction({ + 'res_model': 'ir.ui.view', + 'res_id': data.view_id, + 'views': [[false, 'form']], + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + }); + } else { + this.website.goToWebsite({ path: data.url, edition: true }); + } + }, + }); + } + const action = this.props.context.create_action; + if (action) { + if (/^\//.test(action)) { + window.location.replace(action); + return; + } + this.actionService.doAction(action, { + onClose: (data) => { + if (data) { + this.website.goToWebsite({path: data.path}); + } + }, + }); + } + } -viewRegistry.add('website_page_list', WebsitePageListView); + onSelectWebsite(website) { + this.state.activeWebsite = website; + } +} +PageListController.template = `website.PageListView`; -export default { - WebsitePageListController: WebsitePageListController, - WebsitePageListView: WebsitePageListView, +export class PageListRenderer extends listView.Renderer { + /** + * The goal here is to tweak the renderer to display pages following some + * rules: + * - All websites (props.activeWebsite.id === 0): + * -> Show all generic/specific pages. + * - A website is selected: + * -> Display website-specific pages & generic ones (only those without + * specific clones). + */ + pageFilter(record, records) { + return !this.props.activeWebsite.id + || this.props.activeWebsite.id === record.data.website_id[0] + || !record.data.website_id[0] && records.filter(rec => rec.data.url === record.data.url).length === 1; + } +} +PageListRenderer.props = [ + ...listView.Renderer.props, + "activeWebsite", +]; +PageListRenderer.recordRowTemplate = "website.PageListRenderer.RecordRow"; + +export const PageListView = { + ...listView, + Renderer: PageListRenderer, + Controller: PageListController, }; + +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 49bc995364d..9949e251d05 100644 --- a/addons/website/static/src/components/views/page_list.xml +++ b/addons/website/static/src/components/views/page_list.xml @@ -1,18 +1,42 @@ - -