diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index ef193673f3c..a3db86d7a08 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -692,32 +692,34 @@ class Website(Home):
# Themes
# ------------------------------------------------------
- def _get_customize_views(self, xml_ids):
- View = request.env["ir.ui.view"].with_context(active_test=False)
- if not xml_ids:
- return View
- domain = [("key", "in", xml_ids)] + request.website.website_domain()
- return View.search(domain).filter_duplicate()
+ def _get_customize_data(self, keys, isViewData):
+ model = 'ir.ui.view' if isViewData else 'ir.asset'
+ Data = request.env[model].with_context(active_test=False)
+ if not keys:
+ return Data
+ domain = [("key", "in", keys)] + request.website.website_domain()
+ return Data.search(domain).filter_duplicate()
- @http.route(['/website/theme_customize_get'], type='json', auth='user', website=True)
- def theme_customize_get(self, xml_ids):
- views = self._get_customize_views(xml_ids)
- return views.filtered('active').mapped('key')
+ @http.route(['/website/theme_customize_data_get'], type='json', auth='user', website=True)
+ def theme_customize_data_get(self, keys, isViewData):
+ Data = self._get_customize_data(keys, isViewData)
+ return Data.filtered('active').mapped('key')
- @http.route(['/website/theme_customize'], type='json', auth='user', website=True)
- def theme_customize(self, enable=None, disable=None, reset_view_arch=False):
+ @http.route(['/website/theme_customize_data'], type='json', auth='user', website=True)
+ def theme_customize_data(self, isViewData, enable=None, disable=None, reset_view_arch=False):
"""
- Enables and/or disables views according to list of keys.
+ Enables and/or disables views/assets according to list of keys.
- :param enable: list of views' keys to enable
- :param disable: list of views' keys to disable
+ :param isViewData: True = "ir.ui.view", False = "ir.asset"
+ :param enable: list of views/assets keys to enable
+ :param disable: list of views/assets keys to disable
:param reset_view_arch: restore the default template after disabling
"""
- disabled_views = self._get_customize_views(disable).filtered('active')
+ disabled_data = self._get_customize_data(disable, isViewData).filtered('active')
if reset_view_arch:
- disabled_views.reset_arch(mode='hard')
- disabled_views.write({'active': False})
- self._get_customize_views(enable).filtered(lambda x: not x.active).write({'active': True})
+ disabled_data.reset_arch(mode='hard')
+ disabled_data.write({'active': False})
+ self._get_customize_data(enable, isViewData).filtered(lambda x: not x.active).write({'active': True})
@http.route(['/website/theme_customize_bundle_reload'], type='json', auth='user', website=True)
def theme_customize_bundle_reload(self):
diff --git a/addons/website/models/ir_asset.py b/addons/website/models/ir_asset.py
index acba68f00e4..ddaba61b6e2 100644
--- a/addons/website/models/ir_asset.py
+++ b/addons/website/models/ir_asset.py
@@ -58,3 +58,34 @@ class IrAsset(models.Model):
most_specific_assets += asset
return most_specific_assets
+
+ def write(self, vals):
+ """COW for ir.asset. This way editing websites does not impact other
+ websites. Also this way newly created websites will only
+ contain the default assets.
+ """
+ current_website_id = self.env.context.get('website_id')
+ if not current_website_id or self.env.context.get('no_cow'):
+ return super().write(vals)
+
+ for asset in self.with_context(active_test=False):
+ # No need of COW if the asset is already specific
+ if asset.website_id:
+ super(IrAsset, asset).write(vals)
+ continue
+
+ # If already a specific asset for this generic asset, write on it
+ website_specific_asset = asset.search([
+ ('key', '=', asset.key),
+ ('website_id', '=', current_website_id)
+ ], limit=1)
+ if website_specific_asset:
+ super(IrAsset, website_specific_asset).write(vals)
+ continue
+
+ copy_vals = {'website_id': current_website_id, 'key': asset.key}
+ website_specific_asset = asset.copy(copy_vals)
+
+ super(IrAsset, website_specific_asset).write(vals)
+
+ return True
diff --git a/addons/website/models/theme_models.py b/addons/website/models/theme_models.py
index 526dff97f8f..adcaea7c227 100644
--- a/addons/website/models/theme_models.py
+++ b/addons/website/models/theme_models.py
@@ -221,8 +221,8 @@ class Theme(models.AbstractModel):
)
# Reinitialize effets
- self.disable_asset('website.ripple_effect_scss')
- self.disable_asset('website.ripple_effect_js')
+ self.disable_asset("website.ripple_effect_scss")
+ self.disable_asset("website.ripple_effect_js")
# Reinitialize header templates
self.disable_view('website.template_header_hamburger')
@@ -251,15 +251,15 @@ class Theme(models.AbstractModel):
self.disable_view('website.option_footer_scrolltop')
@api.model
- def _toggle_asset(self, name, active):
+ def _toggle_asset(self, key, active):
ThemeAsset = self.env['theme.ir.asset'].sudo().with_context(active_test=False)
- obj = ThemeAsset.search([('name', '=', name)])
+ obj = ThemeAsset.search([('key', '=', key)])
website = self.env['website'].get_current_website()
if obj:
obj = obj.copy_ids.filtered(lambda x: x.website_id == website)
else:
Asset = self.env['ir.asset'].sudo().with_context(active_test=False)
- obj = Asset.search([('name', '=', name)])
+ obj = Asset.search([('key', '=', key)], limit=1)
has_specific = obj.key and Asset.search_count([
('key', '=', obj.key),
('website_id', '=', website.id)
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js
index 1454fc79d3a..4ca29d192e2 100644
--- a/addons/website/static/src/js/editor/snippets.options.js
+++ b/addons/website/static/src/js/editor/snippets.options.js
@@ -428,6 +428,12 @@ options.Class.include({
customizeWebsiteColor: async function (previewMode, widgetValue, params) {
await this._customizeWebsite(previewMode, widgetValue, params, 'color');
},
+ /**
+ * @see this.selectClass for parameters
+ */
+ async customizeWebsiteAssets(previewMode, widgetValue, params) {
+ await this._customizeWebsite(previewMode, widgetValue, params, 'assets');
+ },
//--------------------------------------------------------------------------
// Private
@@ -474,24 +480,7 @@ options.Class.include({
_computeWidgetState: async function (methodName, params) {
switch (methodName) {
case 'customizeWebsiteViews': {
- const allXmlIDs = this._getXMLIDsFromPossibleValues(params.possibleValues);
- const enabledXmlIDs = await this._rpc({
- route: '/website/theme_customize_get',
- params: {
- 'xml_ids': allXmlIDs,
- },
- });
- let mostXmlIDsStr = '';
- let mostXmlIDsNb = 0;
- for (const xmlIDsStr of params.possibleValues) {
- const enableXmlIDs = xmlIDsStr.split(/\s*,\s*/);
- if (enableXmlIDs.length > mostXmlIDsNb
- && enableXmlIDs.every(xmlID => enabledXmlIDs.includes(xmlID))) {
- mostXmlIDsStr = xmlIDsStr;
- mostXmlIDsNb = enableXmlIDs.length;
- }
- }
- return mostXmlIDsStr; // Need to return the exact same string as in possibleValues
+ return this._getEnabledCustomizeValues(params.possibleValues, true);
}
case 'customizeWebsiteVariable': {
return weUtils.getCSSVariableValue(params.variable);
@@ -499,6 +488,9 @@ options.Class.include({
case 'customizeWebsiteColor': {
return weUtils.getCSSVariableValue(params.color);
}
+ case 'customizeWebsiteAssets': {
+ return this._getEnabledCustomizeValues(params.possibleValues, false);
+ }
}
return this._super(...arguments);
},
@@ -513,7 +505,7 @@ options.Class.include({
switch (type) {
case 'views':
- await this._customizeWebsiteViews(widgetValue, params);
+ await this._customizeWebsiteData(widgetValue, params, true);
break;
case 'variable':
await this._customizeWebsiteVariable(widgetValue, params);
@@ -521,6 +513,9 @@ options.Class.include({
case 'color':
await this._customizeWebsiteColor(widgetValue, params);
break;
+ case 'assets':
+ await this._customizeWebsiteData(widgetValue, params, false);
+ break;
default:
if (params.customCustomization) {
await params.customCustomization.call(this, widgetValue, params);
@@ -585,17 +580,18 @@ options.Class.include({
/**
* @private
*/
- _customizeWebsiteViews: async function (xmlID, params) {
- const allXmlIDs = this._getXMLIDsFromPossibleValues(params.possibleValues);
- const enableXmlIDs = xmlID.split(/\s*,\s*/);
- const disableXmlIDs = allXmlIDs.filter(xmlID => !enableXmlIDs.includes(xmlID));
+ async _customizeWebsiteData(value, params, isViewData) {
+ const allDataKeys = this._getDataKeysFromPossibleValues(params.possibleValues);
+ const enableDataKeys = value.split(/\s*,\s*/);
+ const disableDataKeys = allDataKeys.filter(value => !enableDataKeys.includes(value));
const resetViewArch = !!params.resetViewArch;
return this._rpc({
- route: '/website/theme_customize',
+ route: '/website/theme_customize_data',
params: {
- 'enable': enableXmlIDs,
- 'disable': disableXmlIDs,
+ 'isViewData': isViewData,
+ 'enable': enableDataKeys,
+ 'disable': disableDataKeys,
'reset_view_arch': resetViewArch,
},
});
@@ -603,12 +599,39 @@ options.Class.include({
/**
* @private
*/
- _getXMLIDsFromPossibleValues: function (possibleValues) {
- const allXmlIDs = [];
- for (const xmlIDsStr of possibleValues) {
- allXmlIDs.push(...xmlIDsStr.split(/\s*,\s*/));
+ _getDataKeysFromPossibleValues(possibleValues) {
+ const allDataKeys = [];
+ for (const dataKeysStr of possibleValues) {
+ allDataKeys.push(...dataKeysStr.split(/\s*,\s*/));
}
- return allXmlIDs.filter((v, i, arr) => arr.indexOf(v) === i);
+ return allDataKeys.filter((v, i, arr) => arr.indexOf(v) === i);
+ },
+ /**
+ * @private
+ * @param {Array} possibleValues
+ * @param {Boolean} isViewData true = "ir.ui.view", false = "ir.asset"
+ * @returns {String}
+ */
+ async _getEnabledCustomizeValues(possibleValues, isViewData) {
+ const allDataKeys = this._getDataKeysFromPossibleValues(possibleValues);
+ const enabledValues = await this._rpc({
+ route: '/website/theme_customize_data_get',
+ params: {
+ 'keys': allDataKeys,
+ 'isViewData': isViewData,
+ },
+ });
+ let mostValuesStr = '';
+ let mostValuesNb = 0;
+ for (const valuesStr of possibleValues) {
+ const enableValues = valuesStr.split(/\s*,\s*/);
+ if (enableValues.length > mostValuesNb
+ && enableValues.every(value => enabledValues.includes(value))) {
+ mostValuesStr = valuesStr;
+ mostValuesNb = enableValues.length;
+ }
+ }
+ return mostValuesStr; // Need to return the exact same string as in possibleValues
},
/**
* @private
diff --git a/addons/website/views/snippets/snippets.xml b/addons/website/views/snippets/snippets.xml
index 9588d51bd17..33bef78b838 100644
--- a/addons/website/views/snippets/snippets.xml
+++ b/addons/website/views/snippets/snippets.xml
@@ -1355,7 +1355,7 @@
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index fc5e007599a..e7c59c75f75 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -2042,6 +2042,7 @@
+ website.ripple_effect_scss
Ripple effect SCSS
web.assets_frontend
/website/static/src/scss/options/ripple_effect.scss
@@ -2049,6 +2050,7 @@
+ website.ripple_effect_js
Ripple effect JS
web.assets_frontend
/website/static/src/js/content/ripple_effect.js