[IMP] website: add page properties dialog
The goal of this commit is to add the 'Page Properties' dialog using the 'website.page' form view. The PagePropertiesDialogWrapper was updated to work on pages listView (to clone/remove pages). A boolean field 'is_in_menu' was added to control if page is in menu on page properties dialog. This should be refactored later as a wizard. See merge commit for more information. task-2687506
This commit is contained in:
@@ -176,6 +176,7 @@
|
||||
'website/static/src/components/ace_editor/ace_editor.scss',
|
||||
'website/static/src/components/dialog/*.js',
|
||||
'website/static/src/components/dialog/*.scss',
|
||||
'website/static/src/components/fields/fields.js',
|
||||
'website/static/src/components/switch/switch.js',
|
||||
'website/static/src/components/switch/switch.scss',
|
||||
'website/static/src/components/wysiwyg_adapter/wysiwyg_adapter.js',
|
||||
@@ -278,6 +279,7 @@
|
||||
'website/static/src/xml/website_widget.xml',
|
||||
'website/static/src/xml/theme_preview.xml',
|
||||
'website/static/src/components/dialog/*',
|
||||
'website/static/src/components/fields/fields.xml',
|
||||
'website/static/src/components/editor/editor.xml',
|
||||
'website/static/src/client_actions/*/*.xml',
|
||||
'website/static/src/systray_items/*.xml',
|
||||
|
||||
@@ -13,6 +13,7 @@ from psycopg2 import sql
|
||||
from werkzeug import urls
|
||||
from werkzeug.datastructures import OrderedMultiDict
|
||||
from werkzeug.exceptions import NotFound
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, fields, models, tools, http, release, registry
|
||||
from odoo.addons.http_routing.models.ir_http import RequestUID, slugify, _guess_mimetype, url_for
|
||||
@@ -722,7 +723,8 @@ class Website(models.Model):
|
||||
# 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()
|
||||
website_id = self.env.context.get('website_id', False) or self.get_current_website().id
|
||||
domain_static = [('website_id', '=', 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
|
||||
@@ -761,6 +763,9 @@ class Website(models.Model):
|
||||
key_copy = string
|
||||
inc = 0
|
||||
domain_static = self.get_current_website().website_domain()
|
||||
website_id = self.env.context.get('website_id', False)
|
||||
if website_id:
|
||||
domain_static = [('website_id', 'in', (False, website_id))]
|
||||
while self.env['website.page'].with_context(active_test=False).sudo().search([('key', '=', key_copy)] + domain_static):
|
||||
inc += 1
|
||||
key_copy = string + (inc and "-%s" % inc or "")
|
||||
@@ -779,7 +784,7 @@ class Website(models.Model):
|
||||
return dependencies
|
||||
|
||||
page = self.env['website.page'].browse(int(page_id))
|
||||
website = self.env['website'].browse(self._context.get('website_id'))
|
||||
website = page.website_id or self.get_current_website()
|
||||
url = page.url
|
||||
|
||||
# search for website_page with link
|
||||
@@ -792,7 +797,7 @@ class Website(models.Model):
|
||||
for page in pages:
|
||||
dependencies.setdefault(page_key, [])
|
||||
dependencies[page_key].append({
|
||||
'text': _('Page <b>%s</b> contains a link to this page', page.url),
|
||||
'content': Markup(_("Page <b>%s</b> contains a link to this page")) % page.url,
|
||||
'item': page.name,
|
||||
'link': page.url,
|
||||
})
|
||||
@@ -807,7 +812,7 @@ class Website(models.Model):
|
||||
for view in views:
|
||||
dependencies.setdefault(view_key, [])
|
||||
dependencies[view_key].append({
|
||||
'text': _('Template <b>%s (id:%s)</b> contains a link to this page') % (view.key or view.name, view.id),
|
||||
'content': Markup(_('Template <b>%s (id:%s)</b> contains a link to this page')) % (view.key or view.name, view.id),
|
||||
'link': '/web#id=%s&view_type=form&model=ir.ui.view' % view.id,
|
||||
'item': _('%s (id:%s)') % (view.key or view.name, view.id),
|
||||
})
|
||||
@@ -820,7 +825,7 @@ class Website(models.Model):
|
||||
menu_key = _('Menus')
|
||||
for menu in menus:
|
||||
dependencies.setdefault(menu_key, []).append({
|
||||
'text': _('This page is in the menu <b>%s</b>', menu.name),
|
||||
'content': Markup(_('This page is in the menu <b>%s</b>')) % menu.name,
|
||||
'link': '/web#id=%s&view_type=form&model=website.menu' % menu.id,
|
||||
'item': menu.name,
|
||||
})
|
||||
@@ -840,7 +845,7 @@ class Website(models.Model):
|
||||
return dependencies
|
||||
|
||||
page = self.env['website.page'].browse(int(page_id))
|
||||
website = self.env['website'].browse(self._context.get('website_id'))
|
||||
website = page.website_id or self.get_current_website()
|
||||
key = page.key
|
||||
|
||||
# search for website_page with link
|
||||
@@ -856,7 +861,7 @@ class Website(models.Model):
|
||||
for p in pages:
|
||||
dependencies.setdefault(page_key, [])
|
||||
dependencies[page_key].append({
|
||||
'text': _('Page <b>%s</b> is calling this file', p.url),
|
||||
'content': Markup(_('Page <b>%s</b> is calling this file')) % p.url,
|
||||
'item': p.name,
|
||||
'link': p.url,
|
||||
})
|
||||
@@ -874,7 +879,7 @@ class Website(models.Model):
|
||||
for view in views:
|
||||
dependencies.setdefault(view_key, [])
|
||||
dependencies[view_key].append({
|
||||
'text': _('Template <b>%s (id:%s)</b> is calling this file') % (view.key or view.name, view.id),
|
||||
'content': Markup(_('Template <b>%s (id:%s)</b> is calling this file')) % (view.key or view.name, view.id),
|
||||
'item': _('%s (id:%s)') % (view.key or view.name, view.id),
|
||||
'link': '/web#id=%s&view_type=form&model=ir.ui.view' % view.id,
|
||||
})
|
||||
|
||||
@@ -26,8 +26,10 @@ class Page(models.Model):
|
||||
view_id = fields.Many2one('ir.ui.view', string='View', required=True, ondelete="cascade")
|
||||
website_indexed = fields.Boolean('Is Indexed', default=True)
|
||||
date_publish = fields.Datetime('Publishing Date')
|
||||
# This is needed to be able to display if page is a menu in /website/pages
|
||||
menu_ids = fields.One2many('website.menu', 'page_id', 'Related Menus')
|
||||
# This is needed to be able to control if page is a menu in page properties.
|
||||
# TODO this should be reviewed entirely so that we use a transient model.
|
||||
is_in_menu = fields.Boolean(compute='_compute_website_menu', inverse='_inverse_website_menu')
|
||||
is_homepage = fields.Boolean(compute='_compute_homepage', inverse='_set_homepage', string='Homepage')
|
||||
is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
|
||||
|
||||
@@ -61,6 +63,26 @@ class Page(models.Model):
|
||||
not page.date_publish or page.date_publish < fields.Datetime.now()
|
||||
)
|
||||
|
||||
@api.depends('menu_ids')
|
||||
def _compute_website_menu(self):
|
||||
for page in self:
|
||||
page.is_in_menu = bool(page.menu_ids)
|
||||
|
||||
def _inverse_website_menu(self):
|
||||
for page in self:
|
||||
if page.is_in_menu:
|
||||
if not page.menu_ids:
|
||||
self.env['website.menu'].create({
|
||||
'name': page.name,
|
||||
'url': page.url,
|
||||
'page_id': page.id,
|
||||
'parent_id': page.website_id.menu_id.id,
|
||||
'website_id': page.website_id.id,
|
||||
})
|
||||
elif page.menu_ids:
|
||||
# If the page is no longer in menu, we should remove its website_menu
|
||||
page.menu_ids.unlink()
|
||||
|
||||
def _get_most_specific_pages(self):
|
||||
''' Returns the most specific pages in self. '''
|
||||
ids = []
|
||||
@@ -72,93 +94,6 @@ class Page(models.Model):
|
||||
previous_page = page
|
||||
return self.filtered(lambda page: page.id in ids)
|
||||
|
||||
def get_page_properties(self):
|
||||
self.ensure_one()
|
||||
res = self.read([
|
||||
'id', 'view_id', 'name', 'url', 'website_published', 'website_indexed', 'date_publish',
|
||||
'menu_ids', 'is_homepage', 'website_id', 'visibility', 'groups_id'
|
||||
])[0]
|
||||
if not res['groups_id']:
|
||||
res['group_id'] = self.env.ref('base.group_user').name_get()[0]
|
||||
elif len(res['groups_id']) == 1:
|
||||
res['group_id'] = self.env['res.groups'].browse(res['groups_id']).name_get()[0]
|
||||
del res['groups_id']
|
||||
|
||||
res['visibility_password'] = res['visibility'] == 'password' and self.visibility_password_display or ''
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def save_page_info(self, website_id, data):
|
||||
website = self.env['website'].browse(website_id)
|
||||
page = self.browse(int(data['id']))
|
||||
|
||||
# If URL has been edited, slug it
|
||||
original_url = page.url
|
||||
url = data['url']
|
||||
if not url.startswith('/'):
|
||||
url = '/' + url
|
||||
if page.url != url:
|
||||
url = '/' + slugify(url, max_length=1024, path=True)
|
||||
url = self.env['website'].get_unique_path(url)
|
||||
|
||||
# If name has changed, check for key uniqueness
|
||||
if page.name != data['name']:
|
||||
page_key = self.env['website'].get_unique_key(slugify(data['name']))
|
||||
else:
|
||||
page_key = page.key
|
||||
|
||||
menu = self.env['website.menu'].search([('page_id', '=', int(data['id']))])
|
||||
if not data['is_menu']:
|
||||
# If the page is no longer in menu, we should remove its website_menu
|
||||
if menu:
|
||||
menu.unlink()
|
||||
else:
|
||||
# The page is now a menu, check if has already one
|
||||
if menu:
|
||||
menu.write({'url': url})
|
||||
else:
|
||||
self.env['website.menu'].create({
|
||||
'name': data['name'],
|
||||
'url': url,
|
||||
'page_id': data['id'],
|
||||
'parent_id': website.menu_id.id,
|
||||
'website_id': website.id,
|
||||
})
|
||||
|
||||
# Edits via the page manager shouldn't trigger the COW
|
||||
# mechanism and generate new pages. The user manages page
|
||||
# visibility manually with is_published here.
|
||||
w_vals = {
|
||||
'key': page_key,
|
||||
'name': data['name'],
|
||||
'url': url,
|
||||
'is_published': data['website_published'],
|
||||
'website_indexed': data['website_indexed'],
|
||||
'date_publish': data['date_publish'] or None,
|
||||
'is_homepage': data['is_homepage'],
|
||||
'visibility': data['visibility'],
|
||||
}
|
||||
if page.visibility == 'restricted_group' and data['visibility'] != "restricted_group":
|
||||
w_vals['groups_id'] = False
|
||||
elif 'group_id' in data:
|
||||
w_vals['groups_id'] = [data['group_id']]
|
||||
if 'visibility_pwd' in data:
|
||||
w_vals['visibility_password_display'] = data['visibility_pwd'] or ''
|
||||
|
||||
page.with_context(no_cow=True).write(w_vals)
|
||||
|
||||
# Create redirect if needed
|
||||
if data['create_redirect']:
|
||||
self.env['website.rewrite'].create({
|
||||
'name': data['name'],
|
||||
'redirect_type': data['redirect_type'],
|
||||
'url_from': original_url,
|
||||
'url_to': url,
|
||||
'website_id': website.id,
|
||||
})
|
||||
|
||||
return url
|
||||
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy(self, default=None):
|
||||
if default:
|
||||
@@ -178,8 +113,7 @@ class Page(models.Model):
|
||||
page = self.browse(int(page_id))
|
||||
copy_param = dict(name=page_name or page.name, website_id=self.env['website'].get_current_website().id)
|
||||
if page_name:
|
||||
page_url = '/' + slugify(page_name, max_length=1024, path=True)
|
||||
copy_param['url'] = self.env['website'].get_unique_path(page_url)
|
||||
copy_param['url'] = self.get_valid_page_url(page_name)
|
||||
|
||||
new_page = page.copy(copy_param)
|
||||
# Should not clone menu if the page was cloned from one website to another
|
||||
@@ -209,8 +143,41 @@ class Page(models.Model):
|
||||
return super(Page, self).unlink()
|
||||
|
||||
def write(self, vals):
|
||||
if 'url' in vals and not vals['url'].startswith('/'):
|
||||
vals['url'] = '/' + vals['url']
|
||||
for page in self:
|
||||
website_id = False
|
||||
if vals.get('website_id') or page.website_id:
|
||||
website_id = vals.get('website_id') or page.website_id.id
|
||||
|
||||
# If URL has been edited, slug it
|
||||
if 'url' in vals:
|
||||
url = vals['url']
|
||||
redirect_old_url = redirect_type = None
|
||||
# TODO This should be done another way after the backend/frontend merge
|
||||
if isinstance(url, dict):
|
||||
redirect_old_url = url.get('redirect_old_url')
|
||||
redirect_type = url.get('redirect_type')
|
||||
url = url.get('url')
|
||||
if not url.startswith('/'):
|
||||
url = '/' + url
|
||||
if page.url != url:
|
||||
url = self.get_valid_page_url(url, website_id)
|
||||
page.menu_ids.write({'url': url})
|
||||
if redirect_old_url:
|
||||
self.env['website.rewrite'].create({
|
||||
'name': vals.get('name') or page.name,
|
||||
'redirect_type': redirect_type,
|
||||
'url_from': page.url,
|
||||
'url_to': url,
|
||||
'website_id': website_id,
|
||||
})
|
||||
vals['url'] = url
|
||||
|
||||
# If name has changed, check for key uniqueness
|
||||
if 'name' in vals and page.name != vals['name']:
|
||||
vals['key'] = self.env['website'].with_context(website_id=website_id).get_unique_key(slugify(vals['name']))
|
||||
if 'visibility' in vals:
|
||||
if vals['visibility'] != 'restricted_group':
|
||||
vals['groups_id'] = False
|
||||
self.clear_caches() # write on page == write on view that invalid cache
|
||||
return super(Page, self).write(vals)
|
||||
|
||||
@@ -306,6 +273,9 @@ class Page(models.Model):
|
||||
results = results.filtered(lambda result: filter_page(search, result, results))
|
||||
return results, count
|
||||
|
||||
def get_valid_page_url(self, page_url, website_id=False):
|
||||
url = '/' + slugify(page_url, max_length=1024, path=True)
|
||||
return self.env['website'].with_context(website_id=website_id).get_unique_path(url)
|
||||
|
||||
# this is just a dummy function to be used as ormcache key
|
||||
def _cached_response():
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {CheckBox} from '@web/core/checkbox/checkbox';
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
import {useWowlService} from '@web/legacy/utils';
|
||||
import {WebsiteDialog} from './dialog';
|
||||
import {FormViewDialog} from 'web.view_dialogs';
|
||||
import {standaloneAdapter} from 'web.OwlCompatibility';
|
||||
import {qweb} from 'web.core';
|
||||
|
||||
const {Component, onWillStart, useState, xml, useRef, markup, onWillRender} = owl;
|
||||
|
||||
export class PageDependencies extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
try {
|
||||
this.orm = useService('orm');
|
||||
} catch {
|
||||
// We are in a legacy environment.
|
||||
// TODO check with framework team to know if this is really needed.
|
||||
this.orm = useWowlService('orm');
|
||||
}
|
||||
|
||||
this.dependencies = {};
|
||||
this.depText = '...';
|
||||
this.action = useRef('action');
|
||||
|
||||
onWillStart(() => this.onWillStart());
|
||||
}
|
||||
|
||||
async onWillStart() {
|
||||
this.dependencies = await this.orm.call(
|
||||
'website',
|
||||
this.props.type === 'key' ?
|
||||
'page_search_key_dependencies' :
|
||||
'page_search_dependencies',
|
||||
[this.props.pageId],
|
||||
);
|
||||
if (this.props.mode === 'popover') {
|
||||
this.depText = Object.entries(this.dependencies)
|
||||
.map(dependency => `${dependency[1].length} ${dependency[0].toLowerCase()}`)
|
||||
.join(', ');
|
||||
} else {
|
||||
for (const key of Object.keys(this.dependencies)) {
|
||||
this.dependencies[key] = this.dependencies[key].map(item => {
|
||||
// TODO probably need to refactor this feature so that the
|
||||
// client side is in charge of what the sentences look like
|
||||
// (not server-side HTML).
|
||||
item.contentToDisplay = markup(item.content);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showDependencies() {
|
||||
$(this.action.el).popover({
|
||||
title: this.env._t('Dependencies'),
|
||||
boundary: 'viewport',
|
||||
placement: 'right',
|
||||
trigger: 'focus',
|
||||
content: qweb.render('website.PageDependencies.Tooltip', {dependencies: this.dependencies}),
|
||||
}).popover('toggle');
|
||||
}
|
||||
}
|
||||
PageDependencies.popoverTemplate = xml`
|
||||
<div class="popover o_page_dependencies" role="tooltip">
|
||||
<div class="arrow"/>
|
||||
<h3 class="popover-header"/>
|
||||
<div class="popover-body"/>
|
||||
</div>
|
||||
`;
|
||||
PageDependencies.template = 'website.PageDependencies';
|
||||
PageDependencies.props = {
|
||||
pageId: Number,
|
||||
mode: String,
|
||||
type: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
};
|
||||
|
||||
export class DeletePageDialog extends WebsiteDialog {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.orm = useService('orm');
|
||||
this.website = useService('website');
|
||||
this.title = this.env._t("Delete Page");
|
||||
this.primaryTitle = this.env._t("Ok");
|
||||
|
||||
this.state = useState({
|
||||
confirm: false,
|
||||
});
|
||||
}
|
||||
|
||||
onConfirmCheckboxChange(checked) {
|
||||
this.state.confirm = checked;
|
||||
}
|
||||
|
||||
async primaryClick() {
|
||||
await this.orm.unlink("website.page", [
|
||||
this.props.pageId,
|
||||
]);
|
||||
this.website.goToWebsite();
|
||||
this.close();
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
DeletePageDialog.components = {PageDependencies, CheckBox};
|
||||
DeletePageDialog.bodyTemplate = 'website.DeletePageContent';
|
||||
DeletePageDialog.footerTemplate = 'website.DeletePageAction';
|
||||
DeletePageDialog.props = {
|
||||
...WebsiteDialog.props,
|
||||
pageId: Number,
|
||||
onClose: Function,
|
||||
};
|
||||
|
||||
export class DuplicatePageDialog extends WebsiteDialog {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.orm = useService('orm');
|
||||
this.website = useService('website');
|
||||
|
||||
this.state = useState({
|
||||
name: '',
|
||||
});
|
||||
}
|
||||
|
||||
async primaryClick() {
|
||||
if (this.state.name) {
|
||||
const res = await this.orm.call(
|
||||
'website.page',
|
||||
'clone_page',
|
||||
[this.props.pageId, this.state.name]
|
||||
);
|
||||
this.website.goToWebsite({path: res, edition: true});
|
||||
}
|
||||
this.close();
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
DuplicatePageDialog.bodyTemplate = xml`
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label col-md-3">
|
||||
Page Name
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" t-model="state.name" class="form-control" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
DuplicatePageDialog.props = {
|
||||
...WebsiteDialog.props,
|
||||
onClose: Function,
|
||||
};
|
||||
|
||||
export class PagePropertiesDialogWrapper extends Component {
|
||||
setup() {
|
||||
try {
|
||||
this.websiteService = useService('website');
|
||||
this.dialogService = useService('dialog');
|
||||
this.orm = useService('orm');
|
||||
} catch {
|
||||
// Use services in legacy environment.
|
||||
this.websiteService = useWowlService('website');
|
||||
this.dialogService = useWowlService('dialog');
|
||||
this.orm = useWowlService('orm');
|
||||
}
|
||||
|
||||
onWillRender(this.setDialogWidget);
|
||||
}
|
||||
|
||||
createDialog() {
|
||||
if (this.props.mode === 'clone') {
|
||||
return {
|
||||
component: DuplicatePageDialog,
|
||||
open: () => this.dialogService.add(DuplicatePageDialog, {
|
||||
pageId: this.pageId,
|
||||
onClose: this.props.onClose,
|
||||
}),
|
||||
};
|
||||
} else if (this.props.mode === 'delete') {
|
||||
return {
|
||||
component: DeletePageDialog,
|
||||
open: () => this.dialogService.add(DeletePageDialog, {
|
||||
pageId: this.pageId,
|
||||
onClose: this.props.onClose,
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
const parent = standaloneAdapter({Component});
|
||||
const formViewDialog = new FormViewDialog(parent, this.dialogOptions);
|
||||
formViewDialog.buttons = [...formViewDialog.buttons, ...this.extraButtons];
|
||||
formViewDialog.on('form_dialog_discarded', parent, this.setDialogWidget.bind(this));
|
||||
return formViewDialog;
|
||||
}
|
||||
}
|
||||
|
||||
get pageId() {
|
||||
return this.props.currentPage || (this.websiteService.currentWebsite && this.websiteService.currentWebsite.metadata.mainObject.id);
|
||||
}
|
||||
|
||||
get dialogOptions() {
|
||||
return {
|
||||
res_model: "website.page",
|
||||
res_id: this.pageId,
|
||||
context: {
|
||||
form_view_ref: 'website.website_page_properties_view_form'
|
||||
},
|
||||
title: this.env._t("Page Properties"),
|
||||
size: 'medium',
|
||||
on_saved: (record, changed) => {
|
||||
if (changed) {
|
||||
return this.orm.read("website.page", [this.pageId], ['url']).then(res => {
|
||||
this.websiteService.goToWebsite({websiteId: record.data.website_id.res_id, path: res[0]['url']});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get extraButtons() {
|
||||
const wrapper = this;
|
||||
return [{
|
||||
text: this.env._t("Duplicate Page"),
|
||||
icon: 'fa-clone',
|
||||
classes: 'btn-link ml-auto',
|
||||
click: function () {
|
||||
wrapper.dialogService.add(DuplicatePageDialog, {
|
||||
pageId: wrapper.pageId,
|
||||
onClose: this.close.bind(this),
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
text: this.env._t("Delete Page"),
|
||||
icon: 'fa-trash',
|
||||
classes: 'btn-link',
|
||||
click: function () {
|
||||
wrapper.dialogService.add(DeletePageDialog, {
|
||||
pageId: wrapper.pageId,
|
||||
onClose: this.close.bind(this),
|
||||
});
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
setDialogWidget() {
|
||||
this.props.setPagePropertiesDialog(this.createDialog());
|
||||
}
|
||||
}
|
||||
PagePropertiesDialogWrapper.template = xml``;
|
||||
PagePropertiesDialogWrapper.props = {
|
||||
setPagePropertiesDialog: Function,
|
||||
onClose: {
|
||||
type: Function,
|
||||
optional: true,
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.o_page_dependencies {
|
||||
.popover-body {
|
||||
overflow-y: auto;
|
||||
max-height: 14.2rem;
|
||||
}
|
||||
}
|
||||
@@ -1,185 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<templates>
|
||||
|
||||
<!-- Tooltip Dependencies -->
|
||||
<t t-name="website.PageDependencies" owl="1">
|
||||
<t t-if="!!Object.keys(dependencies).length">
|
||||
<t t-if="props.mode === 'popover'">
|
||||
<t t-set="depTemplate">
|
||||
<t t-call="{{ constructor.popoverTemplate }}"/>
|
||||
</t>
|
||||
<t t-if="props.type === 'key'">
|
||||
<div t-att-data-template="depTemplate" data-html="true" title="Dependencies" t-ref="action">
|
||||
It looks like your file is being called by
|
||||
<a href="#" t-on-click="showDependencies"><t t-esc="depText" /></a>.
|
||||
Changing its name will break these calls.
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div t-att-data-template="depTemplate" data-html="true" title="Dependencies" t-ref="action">
|
||||
(could be used in <a href="#" t-on-click="showDependencies"><t t-esc="depText" /></a>)
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<p class="text-warning">We found these ones:</p>
|
||||
<div t-foreach="dependencies" t-as="dependency" t-key="dependency" class="ml16 mb16">
|
||||
<a class="collapsed fa fa-caret-right" data-toggle="collapse"
|
||||
t-attf-aria-controls="collapseDependencies{{ dependency_index }}"
|
||||
t-attf-href="#collapseDependencies{{ dependency_index }}"
|
||||
aria-expanded="false">
|
||||
<t t-esc="dependency"/>
|
||||
<span class="text-muted"> <t t-esc="dependency_value.length"/> found(s)</span>
|
||||
</a>
|
||||
<div t-attf-id="collapseDependencies{{ dependency_index }}" class="collapse" aria-expanded="false">
|
||||
<ul>
|
||||
<li t-foreach="dependency_value" t-as="item" t-key="item">
|
||||
<a t-att-href="item.link" t-out="item.contentToDisplay"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="website.get_tooltip_dependencies">
|
||||
<t t-foreach="dependencies" t-as="dep">
|
||||
<b><t t-esc="dep"/></b>
|
||||
<t t-name="website.PageDependencies.Tooltip">
|
||||
<t t-foreach="dependencies" t-as="dependency" t-key="dependency">
|
||||
<b><t t-esc="dependency"/></b>
|
||||
<ul>
|
||||
<li t-foreach="dep_value" t-as="item">
|
||||
<a t-att-href="item_value['link']"
|
||||
t-att-title="item_value['item']"
|
||||
class="o_text_overflow">
|
||||
<li t-foreach="dependency_value" t-as="item" t-key="item">
|
||||
<a t-att-href="item_value['link']" t-att-title="item_value['item']" class="o_text_overflow">
|
||||
<t t-esc="item_value['item']"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="website.show_page_key_dependencies">
|
||||
<div class="col-md-9 offset-md-3">
|
||||
<span class="text-muted" id="warn_about_call_message">
|
||||
<t t-set="depTooltip">
|
||||
<t t-call="website.get_tooltip_dependencies"/>
|
||||
</t>
|
||||
It looks like your file is being called by
|
||||
<a href="#" data-toggle="popover" t-att-data-content="depTooltip" data-html="true" title="Dependencies"><t t-esc="dep_text" /></a>.
|
||||
Changing its name will break these calls.
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="website.show_page_dependencies">
|
||||
<t t-set="depTooltip">
|
||||
<t t-call="website.get_tooltip_dependencies"/>
|
||||
</t>
|
||||
(could be used in <a href="#" class="o_dependencies_redirect_link"><t t-esc="dep_text" /></a>)
|
||||
</t>
|
||||
<t t-name="website.page_dependencies_popover">
|
||||
<div class="popover o_redirect_old_url" role="tooltip">
|
||||
<div class="arrow"/>
|
||||
<h3 class="popover-header"/>
|
||||
<div class="popover-body"/>
|
||||
</div>
|
||||
|
||||
<t t-name="website.DeletePageContent" owl="1">
|
||||
<p>Are you sure you want to delete this page ?</p>
|
||||
<p class="text-warning">Don't forget to update all links referring to this page.</p>
|
||||
<PageDependencies pageId="props.pageId" mode="'collapse'"/>
|
||||
<CheckBox onChange.bind="onConfirmCheckboxChange">
|
||||
<p class="text-warning">I am sure about this.</p>
|
||||
</CheckBox>
|
||||
</t>
|
||||
|
||||
<!-- Page Properties -->
|
||||
<t t-name="website.DeletePageAction" t-inherit="website.DialogFooter" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//button[hasclass('btn-primary')]" position="attributes">
|
||||
<attribute name="t-att-disabled">!state.confirm</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<div t-name="website.pagesMenu.page_info" class="o_page_management_info">
|
||||
<form>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item"><a aria-controls="basic_page_info" role="tab" data-toggle="tab" class="nav-link active" href="#basic_page_info">Name</a></li>
|
||||
<li class="nav-item"><a aria-controls="advances_page_info" role="tab" data-toggle="tab" class="nav-link" href="#advances_page_info">Publish</a></li>
|
||||
</ul>
|
||||
<div class="tab-content mt16">
|
||||
<div role="tabpanel" id="basic_page_info" class="tab-pane fade show active">
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label col-md-3" for="page_name">Page Name</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control" id="page_name" t-att-value="widget.page.name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group warn_about_call"></div>
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label col-md-3" for="page_url">Page URL</label>
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" t-att-title="widget.serverUrl"><small><t t-esc="widget.serverUrlTrunc"/></small></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="page_url" t-att-value="widget.page.url" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row ask_for_redirect">
|
||||
<label class="col-form-label col-md-3" for="create_redirect">Redirect Old URL</label>
|
||||
<div class="col-md-2">
|
||||
<a>
|
||||
<label class="o_switch" for="create_redirect" >
|
||||
<input type="checkbox" id="create_redirect"/>
|
||||
<span/>
|
||||
</label>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-7 mt4 o_dependencies_redirect_list_popover">
|
||||
<span class="text-muted" id="dependencies_redirect"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row ask_for_redirect">
|
||||
<label class="col-form-label col-md-3 redirect_type" for="redirect_type">Type</label>
|
||||
<div class="col-md-6 redirect_type">
|
||||
<select class="form-control" id="redirect_type">
|
||||
<option value="301">301 Moved permanently</option>
|
||||
<option value="302">302 Moved temporarily</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" id="advances_page_info" class="tab-pane fade">
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-4" for="is_menu">Show in Top Menu</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="o_switch" for="is_menu" >
|
||||
<input type="checkbox" t-att-checked="widget.page.menu_ids.length > 0 ? true : undefined" id="is_menu"/>
|
||||
<span/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-4" for="is_homepage">Use as Homepage</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="o_switch" for="is_homepage" >
|
||||
<input type="checkbox" t-att-checked="widget.page.is_homepage ? true : undefined" id="is_homepage"/>
|
||||
<span/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-4" for="is_indexed">
|
||||
Indexed
|
||||
<i class="fa fa-question-circle-o" title="Hide this page from search results" role="img" aria-label="Info"></i>
|
||||
</label>
|
||||
<div class="col-md-2">
|
||||
<label class="o_switch" for="is_indexed" >
|
||||
<input type="checkbox" t-att-checked="widget.page.website_indexed ? true : undefined" id="is_indexed"/>
|
||||
<span/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-4" for="is_published">Publish</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="o_switch js_publish_btn" for="is_published">
|
||||
<input type="checkbox" t-att-checked="widget.page.website_published ? true : undefined" id="is_published"/>
|
||||
<span/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-4" for="date_publish">Publishing Date</label>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group date" id="date_publish_container" data-target-input="nearest">
|
||||
<input type="text" class="form-control datetimepicker-input" data-target="#date_publish_container" id="date_publish"/>
|
||||
<div class="input-group-append" data-target="#date_publish_container" data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-4" for="visibility">Visibility</label>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group">
|
||||
<select id="visibility" class="form-control col-md-4">
|
||||
<option value="" t-att-selected="widget.page.visibility == ''">Public</option>
|
||||
<option value="connected" t-att-selected="widget.page.visibility == 'connected' ? 'selected' : undefined">Signed In</option>
|
||||
<option value="restricted_group" t-att-selected="widget.page.visibility == 'restricted_group' ? 'selected' : undefined">Some Users</option>
|
||||
<option value="password" t-att-selected="widget.page.visibility == 'password' ? 'selected' : undefined">Password</option>
|
||||
</select>
|
||||
<div class="ml-1 input-group-prepend show_visibility_password" >
|
||||
<div class="input-group-text"><i class="fa fa-key"></i></div>
|
||||
</div>
|
||||
<input type="password" id="visibility_password"
|
||||
t-att-value='widget.page.visibility_password'
|
||||
t-att-required="widget.page.visibility == 'password' ? 'required' : None"
|
||||
class="form-control show_visibility_password"
|
||||
autocomplete="new-password"/>
|
||||
<t t-if="widget.page.hasSingleGroup">
|
||||
<div class="ml-1 input-group-prepend show_group_id">
|
||||
<div class="input-group-text"><i class="fa fa-group"></i></div>
|
||||
</div>
|
||||
<t t-set="group" t-value="widget.page.group_id"/>
|
||||
<input type="text" class="form-control show_group_id" id="group_id" t-att-value="group ? group[1] : ''" t-att-data-group-id='group ? group[0] : 0' />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set='groups_tooltip'>More than one group has been set on the view.</t>
|
||||
<a class="show_group_id btn btn-link mx-auto" t-attf-href="/web#id=#{widget.page.view_id[0]}&view_type=form&model=ir.ui.view" t-att-title='groups_tooltip'>Discard & Edit in backend</a>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {PageDependencies} from '@website/components/dialog/page_properties';
|
||||
import {Switch} from '@website/components/switch/switch';
|
||||
import AbstractFieldOwl from 'web.AbstractFieldOwl';
|
||||
import fieldRegistry from 'web.field_registry_owl';
|
||||
|
||||
const {useState, onWillStart} = owl;
|
||||
|
||||
class FieldPageUrl extends AbstractFieldOwl {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.state = useState({
|
||||
redirect_old_url: false,
|
||||
url: this.value,
|
||||
redirect_type: '301',
|
||||
});
|
||||
|
||||
this.serverUrl = window.location.origin;
|
||||
this.pageUrl = this.value;
|
||||
}
|
||||
|
||||
get enableRedirect() {
|
||||
return this.state.url !== this.pageUrl;
|
||||
}
|
||||
|
||||
onChangeRedirectOldUrl(value) {
|
||||
this.state.redirect_old_url = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
commitChanges() {
|
||||
if (this.enableRedirect) {
|
||||
this._setValue(this.state);
|
||||
}
|
||||
return super.commitChanges();
|
||||
}
|
||||
}
|
||||
FieldPageUrl.components = {Switch, PageDependencies};
|
||||
FieldPageUrl.supportedFieldTypes = ['char'];
|
||||
FieldPageUrl.template = 'website.FieldPageUrl';
|
||||
|
||||
class FieldPageName extends AbstractFieldOwl {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.state = useState({
|
||||
name: this.value,
|
||||
});
|
||||
|
||||
this.pageName = this.value;
|
||||
this.supportedMimetypes = {};
|
||||
|
||||
onWillStart(() => this.onWillStart());
|
||||
}
|
||||
|
||||
async onWillStart() {
|
||||
this.supportedMimetypes = await this.rpc({
|
||||
model: 'website',
|
||||
method: 'guess_mimetype',
|
||||
});
|
||||
}
|
||||
|
||||
get warnAboutCall() {
|
||||
return this.nameChanged && this.isSupportedMimetype;
|
||||
}
|
||||
|
||||
get nameChanged() {
|
||||
return this.state.name !== this.pageName;
|
||||
}
|
||||
|
||||
get isSupportedMimetype() {
|
||||
const ext = '.' + this.pageName.split('.').pop();
|
||||
return ext in this.supportedMimetypes && ext !== '.html';
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
commitChanges() {
|
||||
if (this.nameChanged) {
|
||||
this._setValue(this.state.name);
|
||||
}
|
||||
return super.commitChanges();
|
||||
}
|
||||
}
|
||||
FieldPageName.components = {PageDependencies};
|
||||
FieldPageName.supportedFieldTypes = ['char'];
|
||||
FieldPageName.template = 'website.FieldPageName';
|
||||
|
||||
fieldRegistry.add('page_url', FieldPageUrl);
|
||||
fieldRegistry.add('page_name', FieldPageName);
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
|
||||
<t t-name="website.FieldPageUrl" owl="1">
|
||||
<div>
|
||||
<div class="d-flex">
|
||||
<div class="input-group-text rounded-left text-lowercase border-0" t-esc="serverUrl.length > 30 ? serverUrl.slice(0,14) + '..' + serverUrl.slice(-14) : serverUrl"/>
|
||||
<input t-model="state.url" type="text" class="o_input"/>
|
||||
</div>
|
||||
<div t-if="enableRedirect">
|
||||
<div class="d-flex mt-4">
|
||||
<Switch extraClasses="'mr-auto'" label="'Redirect Old URL'" value="state.redirect_old_url" onChange="(value) => this.onChangeRedirectOldUrl(value)"/>
|
||||
<PageDependencies pageId="resId" mode="'popover'"/>
|
||||
</div>
|
||||
<div t-if="state.redirect_old_url" class="d-flex mt-4">
|
||||
<label class="my-0 mr-4 font-weight-bold" for="redirect_type">Type</label>
|
||||
<select class="o_input w-50" id="redirect_type" t-model="state.redirect_type">
|
||||
<option value="301">301 Moved permanently</option>
|
||||
<option value="302">302 Moved temporarily</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="website.FieldPageName" owl="1">
|
||||
<div>
|
||||
<input t-model="state.name" type="text" class="o_input"/>
|
||||
<div t-if="warnAboutCall" class="mt-4">
|
||||
<PageDependencies pageId="resId" mode="'popover'" type="'key'"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -7,6 +7,7 @@ import { patch } from 'web.utils';
|
||||
import { EditMenuDialog } from '@website/components/dialog/edit_menu';
|
||||
import { OptimizeSEODialog } from '@website/components/dialog/seo';
|
||||
import {WebsiteAceEditor, AceEditorAdapterComponent} from '@website/components/ace_editor/ace_editor';
|
||||
import {PagePropertiesDialogWrapper} from '@website/components/dialog/page_properties';
|
||||
|
||||
const websiteSystrayRegistry = registry.category('website_systray');
|
||||
const {useState} = owl;
|
||||
@@ -33,6 +34,9 @@ patch(NavBar.prototype, 'website_navbar', {
|
||||
this.toggleAceEditor = (show) => {
|
||||
this.websiteContext.showAceEditor = show;
|
||||
};
|
||||
this.setPagePropertiesDialog = (dialog) => {
|
||||
this.pagePropertiesDialog = dialog;
|
||||
};
|
||||
|
||||
this.websiteEditingMenus = {
|
||||
'website.menu_edit_menu': {
|
||||
@@ -47,6 +51,10 @@ patch(NavBar.prototype, 'website_navbar', {
|
||||
openWidget: () => this.toggleAceEditor(true),
|
||||
isDisplayed: () => this.canShowAceEditor(),
|
||||
},
|
||||
'website.menu_page_properties': {
|
||||
openWidget: () => this.pagePropertiesDialog.open(),
|
||||
isDisplayed: () => this.canShowPageProperties(),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -105,10 +113,16 @@ patch(NavBar.prototype, 'website_navbar', {
|
||||
return this._super(menu);
|
||||
},
|
||||
|
||||
canShowPageProperties() {
|
||||
return this.websiteService.currentWebsite
|
||||
&& !!this.websiteService.currentWebsite.metadata.mainObject
|
||||
&& this.websiteService.currentWebsite.metadata.mainObject.model === 'website.page';
|
||||
},
|
||||
|
||||
canShowAceEditor() {
|
||||
return this.websiteService.pageDocument && this.websiteService.pageDocument.documentElement.dataset.viewXmlid
|
||||
&& !this.websiteContext.showNewContentModal && !this.websiteContext.edition;
|
||||
},
|
||||
});
|
||||
|
||||
NavBar.components.AceEditorAdapterComponent = AceEditorAdapterComponent;
|
||||
NavBar.components = {...NavBar.components, AceEditorAdapterComponent, PagePropertiesDialogWrapper};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<t t-name="website.NavBar" t-inherit="web.NavBar" t-inherit-mode="extension">
|
||||
<xpath expr="//header" position="after">
|
||||
<AceEditorAdapterComponent t-if="canShowAceEditor() and websiteContext.showAceEditor" Component="aceEditor" toggleAceEditor="toggleAceEditor"/>
|
||||
<PagePropertiesDialogWrapper t-if="canShowPageProperties()" setPagePropertiesDialog="setPagePropertiesDialog"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -128,6 +128,48 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="website_page_properties_view_form" model="ir.ui.view">
|
||||
<field name="name">website.page.properties.form.view</field>
|
||||
<field name="model">website.page</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<notebook>
|
||||
<page string="Name">
|
||||
<group class="mt-4">
|
||||
<field name="name" string="Page Name" widget="page_name"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="website_id" invisible="1"/>
|
||||
<field name="url" string="Page URL" widget="page_url" required="True"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Publish">
|
||||
<group class="mt-4">
|
||||
<field name="is_in_menu" string="Show in Top Menu" widget="boolean_toggle"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="is_homepage" string="Use as Homepage" widget="boolean_toggle"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="website_indexed" string="Indexed" widget="boolean_toggle" help="Hide this page from search results"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="is_published" string="Published" widget="boolean_toggle"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_publish" widget="datetime"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="visibility"/>
|
||||
<field name="visibility_password_display" attrs="{'invisible': [('visibility', '!=', 'password')], 'required': [('visibility', '=', 'password')]}" password="True" string="Password"/>
|
||||
<field name="groups_id" attrs="{'invisible': [('visibility', '!=', 'restricted_group')]}" string="Authorized Groups" widget="many2many_tags"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="website_pages_tree_view" model="ir.ui.view">
|
||||
<field name="name">website.page.list</field>
|
||||
<field name="model">website.page</field>
|
||||
@@ -569,6 +611,12 @@
|
||||
name="HTML / CSS Editor"
|
||||
action="website.website_preview"/>
|
||||
|
||||
<menuitem id="menu_page_properties"
|
||||
parent="menu_site"
|
||||
sequence="50"
|
||||
name="Page Properties"
|
||||
action="website.website_preview"/>
|
||||
|
||||
<menuitem id="menu_website_dashboard"
|
||||
name="eCommerce"
|
||||
sequence="1"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, models, _
|
||||
from odoo.addons.http_routing.models.ir_http import url_for
|
||||
|
||||
@@ -26,7 +28,7 @@ class Website(models.Model):
|
||||
dep[page_key] = []
|
||||
for p in posts:
|
||||
dep[page_key].append({
|
||||
'text': _('Blog Post <b>%s</b> seems to have a link to this page !', p.name),
|
||||
'content': Markup(_('Blog Post <b>%s</b> seems to have a link to this page !', p.name)),
|
||||
'item': p.name,
|
||||
'link': p.website_url,
|
||||
})
|
||||
@@ -51,7 +53,7 @@ class Website(models.Model):
|
||||
dep[page_key] = []
|
||||
for p in posts:
|
||||
dep[page_key].append({
|
||||
'text': _('Blog Post <b>%s</b> seems to be calling this file !', p.name),
|
||||
'content': Markup(_('Blog Post <b>%s</b> seems to be calling this file !', p.name)),
|
||||
'item': p.name,
|
||||
'link': p.website_url,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user