[FIX] website, *: restore publish and edit-in-backend as a non-designer

*: website_sale

During the big "website-in-backend" refactoring, [1] and [2] were made
to add the "edit-in-backend" and publish buttons in the systray. At the
same time, the information about if the main object of the iframe is
available to "edit-in-backend" / is published was added on the loaded
iframe's html element (as was already the information about what the
main object is).

The problem is that [1] + [2] were merged around the same time as [3]
which decided to put the existing data addition on the html element...
in a t-if. [1] and [2] ended up adding the new information only if the
user is a designer, while it is actually needed as restricted editor
users. As a result: the publish and "edit-in-backend" buttons did not
appear anymore for restricted editor users.

At the same time, [3] seems wrong on its own, as it moved some
information in the conditional block although we wanted that information
to be available even for visitors (especially, the main object which is
for example used by the product snippet*). This is fixed by this
commit too.
* Steps to reproduce:
- As a restricted editor, go to /shop in the backend iframe
- Click on a category at the top
- Edit
- Add a "Products" snippet in the category-related area
- Click on it and choose "Current" as the value for the category
=> Crash. That crash actually also occurs as a visitor on page load if
such a snippet was saved by an admin first (but the crash in that case
is silent).

Note that the "seo-object" info is not needed by public users but was
restored as a public data as it was the case in 15.0. It might make
sense to some custo. However, the "oe-company-name" info, which was
public in 15.0, is kept shown for designers only as it seems to be dead
code in 16.0 (will be removed later on in master).

Unfortunately, no stable fix can really be made to solve this issue:
this commit had to modify the existing `website.layout` XML. So to solve
this issue in existing databases, you either have to patch the view
accordingly or update the website app. That should be ok, considering
that the only real problem is that some options cannot be used as a
restricted editor user.

[1]: https://github.com/odoo/odoo/commit/f260e3c1441b380d0c9f5294d7cadcaad9c90662
[2]: https://github.com/odoo/odoo/commit/028efcbffe1cbaa00fe6a262151c75fda922008d
[3]: https://github.com/odoo/odoo/commit/b0a2a41d78292cb8b9e53788d40c6dc5915a466d

task-3129034

X-original-commit: bfb6d7820c35f3eba3bb760810866350092a16d9
Part-of: odoo/odoo#111289
This commit is contained in:
qsm-odoo
2023-02-03 05:09:11 +01:00
parent 94e2eefca2
commit c7b80a9360
4 changed files with 137 additions and 6 deletions
+8 -6
View File
@@ -34,23 +34,25 @@
<template id="layout" name="Main layout" inherit_id="portal.frontend_layout">
<xpath expr="//html" position="before">
<t t-set="editable_in_backend" t-value="edit_in_backend or ('website_published' in main_object.fields_get() and main_object._name != 'website.page')"/>
<t t-set="html_data" t-value="{
'lang': lang and lang.replace('_', '-'),
'data-website-id': website.id if website else None,
'data-edit_translations': '1' if edit_translations else None,
'data-main-object': repr(main_object) if main_object else None,
'data-seo-object': repr(seo_object) if seo_object else None,
}"/>
<t t-if="not request.env.user._is_public()" t-set="nothing" t-value="html_data.update({
'data-is-published': 'website_published' in main_object.fields_get() and main_object.website_published,
'data-can-publish': 'can_publish' in main_object.fields_get() and main_object.can_publish,
'data-editable-in-backend': edit_in_backend or ('website_published' in main_object.fields_get() and main_object._name != 'website.page'),
})"/>
<!-- TODO investigate: data-oe-company-name seems like dead code -->
<t t-if="editable or translatable" t-set="nothing" t-value="html_data.update({
'data-editable': '1' if editable else None,
'data-translatable': '1' if translatable else None,
'data-view-xmlid': xmlid,
'data-viewid': viewid,
'data-main-object': repr(main_object) if main_object else None,
'data-seo-object': repr(seo_object) if seo_object else None,
'data-oe-company-name': res_company.name,
'data-is-published': 'website_published' in main_object.fields_get() and main_object.website_published,
'data-can-publish': 'can_publish' in main_object.fields_get() and main_object.can_publish,
'data-editable-in-backend': editable_in_backend,
})"/>
</xpath>
@@ -0,0 +1,59 @@
/** @odoo-modules */
import tour from 'web_tour.tour';
import wTourUtils from 'website.tour_utils';
const PRODUCT_CATEGORY_ID = 2;
wTourUtils.registerWebsitePreviewTour('category_page_and_products_snippet_edition', {
test: true,
url: `/shop/category/${PRODUCT_CATEGORY_ID}`,
edition: true,
}, [
Object.assign(wTourUtils.dragNDrop({id: 's_dynamic_snippet_products', name: 'Products'}), {
content: "Drag and drop the product snippet inside the category area",
run: 'drag_and_drop iframe #category_header',
}),
{
content: "Click on the product snippet to show its options",
trigger: 'iframe #category_header .s_dynamic_snippet_products',
},
{
content: "Open category option dropdown",
trigger: 'we-select[data-attribute-name="productCategoryId"] we-toggler',
},
{
content: "Choose the option to use the current page's category",
trigger: 'we-button[data-select-data-attribute="current"]',
},
...wTourUtils.clickOnSave(),
]);
tour.register('category_page_and_products_snippet_use', {
test: true,
url: `/shop/category/${PRODUCT_CATEGORY_ID}`,
}, [
{
content: "Check that the snippet displays the right products",
// Wait for at least one shown product
trigger: '#category_header .s_dynamic_snippet_products:has(.o_carousel_product_img_link)',
run: function (actions) {
// Note: this could be more robust to not rely on demo data and
// make sure that the newest products are not by chance all of
// the second category (used for the test) and ... but should be ok.
const productGridEl = this.$anchor[0].closest('#products_grid');
const regex = new RegExp(`^/shop/[\\w-/]+-(\\d+)\\?category=${PRODUCT_CATEGORY_ID}$`);
const allPageProductIDs = [...productGridEl.querySelectorAll('.oe_product_image_link')]
.map(el => el.getAttribute('href').match(regex)[1]);
const $shownProductLinks = this.$anchor.find('.o_carousel_product_img_link');
const regex2 = new RegExp(`^/shop/[\\w-/]+-(\\d+)(?:#attr=\\d*)?$`);
for (const shownProductLinkEl of $shownProductLinks) {
const productID = shownProductLinkEl.getAttribute('href').match(regex2)[1];
if (!allPageProductIDs.includes(productID)) {
console.error(`The snippet displays a product (${productID}) which does not belong to the current category (${allPageProductIDs})`);
}
}
},
},
]);
@@ -0,0 +1,43 @@
/** @odoo-modules */
import wTourUtils from 'website.tour_utils';
wTourUtils.registerWebsitePreviewTour('website_sale_restricted_editor_ui', {
test: true,
url: `/shop`,
}, [
{
content: "Open the site menu to check what is inside",
trigger: '[data-menu-xmlid="website.menu_site"]',
},
{
// Not very robust but still nice to have an extra check as this is the
// main purpose of this tour
content: "First check the user is not a designer",
trigger: '.dropdown-menu:has([data-menu-xmlid="website_sale.menu_product_pages"]):not(:has([data-menu-xmlid="website.menu_website_pages_list"]))',
},
{
content: "Ensure the publish and 'edit-in-backend' buttons are not shown",
trigger: '.o_menu_systray:not(:has(.o_switch_danger_success)):not(:has(.o_website_edit_in_backend))',
// Wait for the possibility to edit to appear
extra_trigger: '.o_menu_systray .o_edit_website_container a',
},
{
content: "Navigate to the first product",
trigger: 'iframe .oe_product_image_link',
},
{
content: "Click on publish/unpublish",
trigger: '.o_menu_systray_item .o_switch_danger_success:has(input:checked)',
},
{
content: "Click on edit-in-backend",
trigger: '.o_menu_systray .o_website_edit_in_backend a',
extra_trigger: '.o_menu_systray_item:not([data-processing]) .o_switch_danger_success:has(input:not(:checked))',
},
{
content: "Check that you landed on a form view and that the record was unpublished",
trigger: '.o_form_sheet [name="is_published"] .fa-globe.text-danger',
run: () => {},
},
]);
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.addons.website_sale.controllers.main import WebsiteSale
from odoo.addons.website.tools import MockRequest
from odoo.exceptions import ValidationError
@@ -184,3 +185,29 @@ class TestProductPictureController(HttpCase):
images[1].id,
'first',
)
@tagged('post_install', '-at_install')
class TestWebsiteSaleEditor(HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env['res.users'].create({
'name': 'Restricted Editor',
'login': 'restricted',
'password': 'restricted',
'groups_id': [Command.set([
cls.env.ref('base.group_user').id,
cls.env.ref('sales_team.group_sale_manager').id,
cls.env.ref('website.group_website_restricted_editor').id
])]
})
def test_category_page_and_products_snippet(self):
SHOP_CATEGORY_ID = 2
self.start_tour(self.env['website'].get_client_action_url(f'/shop/category/{SHOP_CATEGORY_ID}'), 'category_page_and_products_snippet_edition', login='restricted')
self.start_tour(f'/shop/category/{SHOP_CATEGORY_ID}', 'category_page_and_products_snippet_use', login=None)
def test_website_sale_restricted_editor_ui(self):
self.start_tour(self.env['website'].get_client_action_url('/shop'), 'website_sale_restricted_editor_ui', login='restricted')