diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py
index d4ff41de09d..83320fef24f 100644
--- a/addons/web_editor/controllers/main.py
+++ b/addons/web_editor/controllers/main.py
@@ -4,20 +4,18 @@ import base64
import io
import json
import logging
-import os
import re
import time
-import uuid
import werkzeug.wrappers
from PIL import Image, ImageFont, ImageDraw
-from lxml import etree, html
+from lxml import etree
from odoo.http import request
from odoo import http, tools
-from odoo.modules.module import get_resource_path, get_module_path
logger = logging.getLogger(__name__)
+
class Web_Editor(http.Controller):
#------------------------------------------------------
# convert font into picture
@@ -296,40 +294,57 @@ class Web_Editor(http.Controller):
result['originalSrc'] = record.url
return result
- def _get_view_fields_to_read(self):
- return ['name', 'id', 'key', 'xml_id', 'arch', 'active', 'inherit_id']
-
- ## The get_assets_editor_resources route is in charge of transmitting the resources the assets
- ## editor needs to work.
- ## @param key - the xml_id or id of the view the resources are related to
- ## @param get_views - True if the views must be fetched (default to True)
- ## @param get_scss - True if the style must be fetched (default to True)
- ## @param get_js - True if the javascript must be fetched (default to True)
- ## @param bundles - True if the bundles views must be fetched (default to False)
- ## @param bundles_restriction - Names of the bundle in which to look for scss files (if empty, search in all of them)
- ## @param only_user_custom_files - True if only user custom files are needed (default to True)
- ## @returns a dictionary with views info in the views key and style info in the scss key
@http.route("/web_editor/get_assets_editor_resources", type="json", auth="user", website=True)
def get_assets_editor_resources(self, key, get_views=True, get_scss=True, get_js=True, bundles=False, bundles_restriction=[], only_user_custom_files=True):
+ """
+ Transmit the resources the assets editor needs to work.
+
+ Params:
+ key (str): the key of the view the resources are related to
+
+ get_views (bool, default=True):
+ True if the views must be fetched
+
+ get_scss (bool, default=True):
+ True if the style must be fetched
+
+ get_js (bool, default=True):
+ True if the javascript must be fetched
+
+ bundles (bool, default=False):
+ True if the bundles views must be fetched
+
+ bundles_restriction (list, default=[]):
+ Names of the bundles in which to look for scss files
+ (if empty, search in all of them)
+
+ only_user_custom_files (bool, default=True):
+ True if only user custom files must be fetched
+
+ Returns:
+ dict: views, scss, js
+ """
# Related views must be fetched if the user wants the views and/or the style
views = request.env["ir.ui.view"].get_related_views(key, bundles=bundles)
- views = views.read(self._get_view_fields_to_read())
+ views = views.read(request.env['web_editor.assets'].get_view_fields_to_read())
- js_files_data_by_bundle = []
scss_files_data_by_bundle = []
+ js_files_data_by_bundle = []
- if get_js:
- js_files_data_by_bundle = self.load_resources('js', views, bundles_restriction, only_user_custom_files)
if get_scss:
- scss_files_data_by_bundle = self.load_resources('scss', views, bundles_restriction, only_user_custom_files)
+ scss_files_data_by_bundle = self._load_resources('scss', views, bundles_restriction, only_user_custom_files)
+ if get_js:
+ js_files_data_by_bundle = self._load_resources('js', views, bundles_restriction, only_user_custom_files)
- return dict(
- views=get_views and views or [],
- scss=get_scss and scss_files_data_by_bundle or [],
- js=get_js and js_files_data_by_bundle or [],
- )
+ return {
+ 'views': get_views and views or [],
+ 'scss': get_scss and scss_files_data_by_bundle or [],
+ 'js': get_js and js_files_data_by_bundle or [],
+ }
+
+ def _load_resources(self, file_type, views, bundles_restriction, only_user_custom_files):
+ AssetsUtils = request.env['web_editor.assets']
- def load_resources(self, file_type, views, bundles_restriction, only_user_custom_files):
files_data_by_bundle = []
resources_type_info = {'t_call_assets_attribute': 't-js', 'mimetype': 'text/javascript'}
if file_type == 'scss':
@@ -339,10 +354,6 @@ class Web_Editor(http.Controller):
# This will used to exclude library scss files from the result
excluded_url_matcher = re.compile("^(.+/lib/.+)|(.+import_bootstrap.+\.scss)$")
- # Load already customized files attachments
- custom_url = self._make_custom_scss_or_js_file_url("%%.%%", "%%")
- custom_attachments = self.get_custom_attachment(custom_url, op='=like')
-
# First check the t-call-assets used in the related views
url_infos = dict()
for v in views:
@@ -363,7 +374,7 @@ class Web_Editor(http.Controller):
continue
# Check if the file is customized and get bundle/path info
- file_data = self._match_scss_or_js_file_url(url)
+ file_data = AssetsUtils.get_asset_info(url)
if not file_data:
continue
@@ -375,13 +386,19 @@ class Web_Editor(http.Controller):
or file_type == 'scss' and not only_user_custom_files:
files_data.append(url)
- # File data is returned sorted by bundle, with the bundles names and xmlids
+ # scss data is returned sorted by bundle, with the bundles
+ # names and xmlids
if len(files_data):
- files_data_by_bundle.append([dict(xmlid=asset_name, name=request.env.ref(asset_name).name), files_data])
+ files_data_by_bundle.append([
+ {'xmlid': asset_name, 'name': request.env.ref(asset_name).name},
+ files_data
+ ])
# Filter bundles/files:
- # - A file which appears in multiple bundles only appears in the first one (the first in the DOM)
- # - Only keep bundles with files which appears in the asked bundles and only keep those files
+ # - A file which appears in multiple bundles only appears in the
+ # first one (the first in the DOM)
+ # - Only keep bundles with files which appears in the asked bundles
+ # and only keep those files
for i in range(0, len(files_data_by_bundle)):
bundle_1 = files_data_by_bundle[i]
for j in range(0, len(files_data_by_bundle)):
@@ -393,7 +410,8 @@ class Web_Editor(http.Controller):
bundle_1 = files_data_by_bundle[i]
for j in range(i + 1, len(files_data_by_bundle)):
bundle_2 = files_data_by_bundle[j]
- # In every bundle, keep only the files which were not found in previous bundles
+ # In every bundle, keep only the files which were not found
+ # in previous bundles
bundle_2[1] = [item_2 for item_2 in bundle_2[1] if item_2 not in bundle_1[1]]
# Only keep bundles which still have files and that were requested
@@ -403,146 +421,57 @@ class Web_Editor(http.Controller):
]
# Fetch the arch of each kept file, in each bundle
+ urls = []
+ for bundle_data in files_data_by_bundle:
+ urls += bundle_data[1]
+ custom_attachments = AssetsUtils.get_all_custom_attachments(urls)
+
for bundle_data in files_data_by_bundle:
for i in range(0, len(bundle_data[1])):
url = bundle_data[1][i]
url_info = url_infos[url]
- content = None
- if url_info["customized"]:
- # If the file is already customized, the content is found in the corresponding attachment
- content = base64.b64decode(custom_attachments.filtered(lambda a: a.url == url).datas)
- else:
- # If the file is not yet customized, the content is found by reading the local scss file
- module = url_info["module"]
- module_path = get_module_path(module)
- module_resource_path = get_resource_path(module, url_info["resource_path"])
- if module_path and module_resource_path:
- module_path = os.path.join(os.path.normpath(module_path), '') # join ensures the path ends with '/'
- module_resource_path = os.path.normpath(module_resource_path)
- if module_resource_path.startswith(module_path):
- with open(module_resource_path, "rb") as f:
- content = f.read()
+ content = AssetsUtils.get_asset_content(url, url_info, custom_attachments)
+
+ bundle_data[1][i] = {
+ 'url': "/%s/%s" % (url_info["module"], url_info["resource_path"]),
+ 'arch': content,
+ 'customized': url_info["customized"],
+ }
- bundle_data[1][i] = dict(
- url="/%s/%s" % (url_info["module"], url_info["resource_path"]),
- arch=content,
- customized=url_info["customized"],
- )
return files_data_by_bundle
- def save_scss_or_js_view_hook(self):
- return {}
+ @http.route("/web_editor/save_asset", type="json", auth="user", website=True)
+ def save_asset(self, url, bundle_xmlid, content, file_type):
+ """
+ Save a given modification of a scss/js file.
- def save_scss_or_js_attachment_hook(self):
- return {}
+ Params:
+ url (str):
+ the original url of the scss/js file which has to be modified
- ## The save_scss_or_js route is in charge of saving a given modification of a scss/js file.
- ## @param url - the original url of the scss/js file which has to be modified
- ## @param bundle_xmlid - the xmlid of the bundle in which the scss/js file addition can be found
- ## @param content - the new content of the scss/js file
- ## @param file_type - scss or js
- @http.route("/web_editor/save_scss_or_js", type="json", auth="user", website=True)
- def save_scss_or_js(self, url, bundle_xmlid, content, file_type):
- IrAttachment = request.env["ir.attachment"]
+ bundle_xmlid (str):
+ the xmlid of the bundle in which the scss/js file addition can
+ be found
- custom_url = self._make_custom_scss_or_js_file_url(url, bundle_xmlid)
+ content (str): the new content of the scss/js file
- # Check if the file to save had already been modified
- custom_attachment = self.get_custom_attachment(custom_url)
- datas = base64.b64encode((content or "\n").encode("utf-8"))
- if custom_attachment:
- # If it was already modified, simply override the corresponding attachment content
- custom_attachment.write({"datas": datas})
- else:
- # If not, create a new attachment to copy the original scss/js file content, with its modifications
- new_attach = {
- 'name': custom_url,
- 'type': "binary",
- 'mimetype': (file_type == 'js' and 'text/javascript' or 'text/scss'),
- 'datas': datas,
- 'datas_fname': url.split("/")[-1],
- 'url': custom_url,
- }
- new_attach.update(self.save_scss_or_js_attachment_hook())
- IrAttachment.create(new_attach)
+ file_type (str): 'scss' or 'js'
+ """
+ request.env['web_editor.assets'].save_asset(url, bundle_xmlid, content, file_type)
- # Create a view to extend the template which adds the original file to link the new modified version instead
- IrUiView = request.env["ir.ui.view"]
- file_type_info = {
- 'tag': 'link' if file_type == 'scss' else 'script',
- 'attribute': 'href' if file_type == 'scss' else 'src',
- }
+ @http.route("/web_editor/reset_asset", type="json", auth="user", website=True)
+ def reset_asset(self, url, bundle_xmlid):
+ """
+ The reset_asset route is in charge of reverting all the changes that
+ were done to a scss/js file.
- def views_linking_url(view):
- """
- Returns whether the view arch has some html link tag linked to the url.
+ Params:
+ url (str):
+ the original URL of the scss/js file to reset
- (note: searching for the URL string is not enough as it could appear in a comment or an xpath expression.)
- """
- tree = etree.XML(view.arch)
- return bool(tree.xpath("//%%(tag)s[@%%(attribute)s='%(url)s']" % {
- 'url': url,
- } % file_type_info))
-
- view_to_xpath = IrUiView.get_related_views(bundle_xmlid, bundles=True).filtered(views_linking_url)
-
- new_view = {
- 'name': custom_url,
- 'key': 'web_editor.%s_%s' % (file_type, str(uuid.uuid4())[:6]),
- 'mode': "extension",
- 'inherit_id': view_to_xpath.id,
- 'arch': """
-
-
- %(new_url)s
-
-
- """ % {
- 'inherit_xml_id': view_to_xpath.xml_id,
- 'name': custom_url,
- 'url_to_replace': url,
- 'new_url': custom_url,
- } % file_type_info
- }
- new_view.update(self.save_scss_or_js_view_hook())
- IrUiView.create(new_view)
-
- request.env["ir.qweb"].clear_caches()
-
- ## The reset_scss_or_js route is in charge of reverting all the changes that were done to a scss/js file.
- ## @param url - the original URL of the scss file to reset
- ## @param bundle_xmlid - the xmlid of the bundle in which the scss file addition can be found
- @http.route("/web_editor/reset_scss_or_js", type="json", auth="user", website=True)
- def reset_scss_or_js(self, url, bundle_xmlid):
- custom_url = self._make_custom_scss_or_js_file_url(url, bundle_xmlid)
-
- # Simply delete the attachement which contains the modified scss/js file and the xpath view which links it
- self.get_custom_attachment(custom_url).unlink()
- self.get_custom_view(custom_url).unlink()
-
- def get_custom_attachment(self, custom_url, op='='):
- assert op in ('=like', '='), 'Invalid operator'
- IrAttachment = request.env["ir.attachment"]
- return IrAttachment.search([("url", op, custom_url)])
-
- def get_custom_view(self, custom_url, op='='):
- assert op in ('=like', '='), 'Invalid operator'
- IrUiView = request.env["ir.ui.view"]
- return IrUiView.search([("name", op, custom_url)])
-
- def _make_custom_scss_or_js_file_url(self, url, bundle):
- parts = url.rsplit(".", 1)
- return "%s.custom.%s.%s" % (parts[0], bundle, parts[1])
-
- _match_file_url_regex = re.compile("^/(\w+)/(.+?)(\.custom\.(.+))?\.(\w+)$")
- def _match_scss_or_js_file_url(self, url):
- m = self._match_file_url_regex.match(url)
- if not m:
- return False
- return {
- 'module': m.group(1),
- 'resource_path': "%s.%s" % (m.group(2), m.group(5)),
- 'customized': bool(m.group(3)),
- 'bundle': m.group(4) or False
- }
+ bundle_xmlid (str):
+ the xmlid of the bundle in which the scss/js file addition can
+ be found
+ """
+ request.env['web_editor.assets'].reset_asset(url, bundle_xmlid)
diff --git a/addons/web_editor/models/__init__.py b/addons/web_editor/models/__init__.py
index b895b36ca54..e9a790f7e61 100644
--- a/addons/web_editor/models/__init__.py
+++ b/addons/web_editor/models/__init__.py
@@ -7,4 +7,6 @@ from . import ir_ui_view
from . import ir_http
from . import ir_translation
+from . import assets
+
from . import test_models
diff --git a/addons/web_editor/models/assets.py b/addons/web_editor/models/assets.py
new file mode 100644
index 00000000000..9406fc495b2
--- /dev/null
+++ b/addons/web_editor/models/assets.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import base64
+import os
+import re
+import uuid
+
+from lxml import etree
+
+from odoo import models
+from odoo.modules.module import get_resource_path, get_module_path
+
+_match_asset_file_url_regex = re.compile("^/(\w+)/(.+?)(\.custom\.(.+))?\.(\w+)$")
+
+
+class Assets(models.AbstractModel):
+ _name = 'web_editor.assets'
+ _description = 'Assets Utils'
+
+ def get_view_fields_to_read(self):
+ return ['name', 'id', 'key', 'xml_id', 'arch', 'active', 'inherit_id']
+
+ def get_all_custom_attachments(self, urls):
+ """
+ Fetch all the ir.attachment records related to given URLs.
+
+ Params:
+ urls (str[]): list of urls
+
+ Returns:
+ ir.attachment(): attachment records related to the given URLs.
+ """
+ return self._get_custom_attachment(urls, op='in')
+
+ def get_asset_content(self, url, url_info=None, custom_attachments=None):
+ """
+ Fetch the content of an asset (scss / js) file. That content is either
+ the one of the related file on the disk or the one of the corresponding
+ custom ir.attachment record.
+
+ Params:
+ url (str): the URL of the asset (scss / js) file/ir.attachment
+
+ url_info (dict, optional):
+ the related url info (see get_asset_info) (allows to optimize
+ some code which already have the info and do not want this
+ function to re-get it)
+
+ custom_attachments (ir.attachment(), optional):
+ the related custom ir.attachment records the function might need
+ to search into (allows to optimize some code which already have
+ that info and do not want this function to re-get it)
+
+ Returns:
+ utf-8 encoded content of the asset (scss / js)
+ """
+ if url_info is None:
+ url_info = self.get_asset_info(url)
+
+ if url_info["customized"]:
+ # If the file is already customized, the content is found in the
+ # corresponding attachment
+ attachment = None
+ if custom_attachments is None:
+ attachment = self._get_custom_attachment(url)
+ else:
+ attachment = custom_attachments.filtered(lambda r: r.url == url)
+ return attachment and base64.b64decode(attachment.datas) or False
+
+ # If the file is not yet customized, the content is found by reading
+ # the local scss file
+ module = url_info["module"]
+ module_path = get_module_path(module)
+ module_resource_path = get_resource_path(module, url_info["resource_path"])
+ if module_path and module_resource_path:
+ module_path = os.path.join(os.path.normpath(module_path), '') # join ensures the path ends with '/'
+ module_resource_path = os.path.normpath(module_resource_path)
+ if module_resource_path.startswith(module_path):
+ with open(module_resource_path, "rb") as f:
+ return f.read()
+
+ def get_asset_info(self, url):
+ """
+ Return information about an asset (scss / js) file/ir.attachment just by
+ looking at its URL.
+
+ Params:
+ url (str): the url of the asset (scss / js) file/ir.attachment
+
+ Returns:
+ dict:
+ module (str): the original asset's related app
+
+ resource_path (str):
+ the relative path to the original asset from the related app
+
+ customized (bool): whether the asset is a customized one or not
+
+ bundle (str):
+ the name of the bundle the asset customizes (False if this
+ is not a customized asset)
+ """
+ m = _match_asset_file_url_regex.match(url)
+ if not m:
+ return False
+ return {
+ 'module': m.group(1),
+ 'resource_path': "%s.%s" % (m.group(2), m.group(5)),
+ 'customized': bool(m.group(3)),
+ 'bundle': m.group(4) or False
+ }
+
+ def make_custom_asset_file_url(self, url, bundle_xmlid):
+ """
+ Return the customized version of an asset URL, that is the URL the asset
+ would have if it was customized.
+
+ Params:
+ url (str): the original asset's url
+ bundle_xmlid (str): the name of the bundle the asset would customize
+
+ Returns:
+ str: the URL the given asset would have if it was customized in the
+ given bundle
+ """
+ parts = url.rsplit(".", 1)
+ return "%s.custom.%s.%s" % (parts[0], bundle_xmlid, parts[1])
+
+ def reset_asset(self, url, bundle_xmlid):
+ """
+ Delete the potential customizations made to a given (original) asset.
+
+ Params:
+ url (str): the URL of the original asset (scss / js) file
+
+ bundle_xmlid (str):
+ the name of the bundle in which the customizations to delete
+ were made
+ """
+ custom_url = self.make_custom_asset_file_url(url, bundle_xmlid)
+
+ # Simply delete the attachement which contains the modified scss/js file
+ # and the xpath view which links it
+ self._get_custom_attachment(custom_url).unlink()
+ self._get_custom_view(custom_url).unlink()
+
+ def save_asset(self, url, bundle_xmlid, content, file_type):
+ """
+ Customize the content of a given asset (scss / js).
+
+ Params:
+ url (src):
+ the URL of the original asset to customize (whether or not the
+ asset was already customized)
+
+ bundle_xmlid (src):
+ the name of the bundle in which the customizations will take
+ effect
+
+ content (src): the new content of the asset (scss / js)
+
+ file_type (src):
+ either 'scss' or 'js' according to the file being customized
+ """
+ custom_url = self.make_custom_asset_file_url(url, bundle_xmlid)
+ datas = base64.b64encode((content or "\n").encode("utf-8"))
+
+ # Check if the file to save had already been modified
+ custom_attachment = self._get_custom_attachment(custom_url)
+ if custom_attachment:
+ # If it was already modified, simply override the corresponding
+ # attachment content
+ custom_attachment.write({"datas": datas})
+ else:
+ # If not, create a new attachment to copy the original scss/js file
+ # content, with its modifications
+ new_attach = {
+ 'name': custom_url,
+ 'type': "binary",
+ 'mimetype': (file_type == 'js' and 'text/javascript' or 'text/scss'),
+ 'datas': datas,
+ 'datas_fname': url.split("/")[-1],
+ 'url': custom_url,
+ }
+ new_attach.update(self._save_asset_attachment_hook())
+ self.env["ir.attachment"].create(new_attach)
+
+ # Create a view to extend the template which adds the original file
+ # to link the new modified version instead
+ file_type_info = {
+ 'tag': 'link' if file_type == 'scss' else 'script',
+ 'attribute': 'href' if file_type == 'scss' else 'src',
+ }
+
+ def views_linking_url(view):
+ """
+ Returns whether the view arch has some html tag linked to
+ the url. (note: searching for the URL string is not enough as it
+ could appear in a comment or an xpath expression.)
+ """
+ tree = etree.XML(view.arch)
+ return bool(tree.xpath("//%%(tag)s[@%%(attribute)s='%(url)s']" % {
+ 'url': url,
+ } % file_type_info))
+
+ IrUiView = self.env["ir.ui.view"]
+ view_to_xpath = IrUiView.get_related_views(bundle_xmlid, bundles=True).filtered(views_linking_url)
+ new_view = {
+ 'name': custom_url,
+ 'key': 'web_editor.%s_%s' % (file_type, str(uuid.uuid4())[:6]),
+ 'mode': "extension",
+ 'inherit_id': view_to_xpath.id,
+ 'arch': """
+
+
+ %(new_url)s
+
+
+ """ % {
+ 'inherit_xml_id': view_to_xpath.xml_id,
+ 'name': custom_url,
+ 'url_to_replace': url,
+ 'new_url': custom_url,
+ } % file_type_info
+ }
+ new_view.update(self._save_asset_view_hook())
+ IrUiView.create(new_view)
+
+ self.env["ir.qweb"].clear_caches()
+
+ def _get_custom_attachment(self, custom_url, op='='):
+ """
+ Fetch the ir.attachment record related to the given customized asset.
+
+ Params:
+ custom_url (str): the URL of the customized asset
+ op (str, default: '='): the operator to use to search the records
+
+ Returns:
+ ir.attachment()
+ """
+ assert op in ('in', '='), 'Invalid operator'
+ return self.env["ir.attachment"].search([("url", op, custom_url)])
+
+ def _get_custom_view(self, custom_url, op='='):
+ """
+ Fetch the ir.ui.view record related to the given customized asset (the
+ inheriting view which replace the original asset by the customized one).
+
+ Params:
+ custom_url (str): the URL of the customized asset
+ op (str, default: '='): the operator to use to search the records
+
+ Returns:
+ ir.ui.view()
+ """
+ assert op in ('='), 'Invalid operator'
+ return self.env["ir.ui.view"].search([("name", op, custom_url)])
+
+ def _save_asset_attachment_hook(self):
+ """
+ Returns the additional values to use to write the DB on customized
+ attachment creation.
+
+ Returns:
+ dict
+ """
+ return {}
+
+ def _save_asset_view_hook(self):
+ """
+ Returns the additional values to use to write the DB on customized
+ asset's related view creation.
+
+ Returns:
+ dict
+ """
+ return {}
diff --git a/addons/web_editor/static/src/js/common/ace.js b/addons/web_editor/static/src/js/common/ace.js
index f231bb0aac6..f242129c4a8 100644
--- a/addons/web_editor/static/src/js/common/ace.js
+++ b/addons/web_editor/static/src/js/common/ace.js
@@ -535,7 +535,7 @@ var ViewEditor = Widget.extend({
} else {
var resource = type === 'scss' ? this.scss[resID] : this.js[resID];
return this._rpc({
- route: '/web_editor/reset_scss_or_js',
+ route: '/web_editor/reset_asset',
params: {
url: resID,
bundle_xmlid: resource.bundle_xmlid,
@@ -560,7 +560,7 @@ var ViewEditor = Widget.extend({
var bundleXmlID = sessionIdEndsWithJS ? this.js[session.id].bundle_xmlid : this.scss[session.id].bundle_xmlid;
var fileType = sessionIdEndsWithJS ? 'js' : 'scss';
self._rpc({
- route: '/web_editor/save_scss_or_js',
+ route: '/web_editor/save_asset',
params: {
url: session.id,
bundle_xmlid: bundleXmlID,
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 6ba749bc3c2..a2d1d332f80 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -4,6 +4,7 @@ import base64
import datetime
import json
import os
+import re
import logging
import requests
import werkzeug.utils
@@ -368,6 +369,42 @@ class Website(Home):
self.theme_customize(enable and enable.split(",") or [], disable and disable.split(",") or [])
return request.redirect(href + ("&theme=true" if "#" in href else "#theme=true") + ("&tab=" + tab))
+ @http.route(['/website/make_scss_custo'], type='json', auth='user', website=True)
+ def make_scss_custo(self, url, values):
+ """
+ Makes a scss customization of the given file. That file must
+ contain a scss map including a line comment containing the word 'hook',
+ to indicate the location where to write the new key,value pairs.
+
+ Params:
+ url (str):
+ the URL of the scss file to customize (supposed to be a variable
+ file which will appear in the assets_common bundle)
+
+ values (dict):
+ key,value mapping to integrate in the file's map (containing the
+ word hook). If a key is already in the file's map, its value is
+ overridden.
+ """
+ AssetsUtils = request.env['web_editor.assets']
+
+ custom_url = AssetsUtils.make_custom_asset_file_url(url, 'web.assets_common')
+ updatedFileContent = AssetsUtils.get_asset_content(custom_url) or AssetsUtils.get_asset_content(url)
+ updatedFileContent = updatedFileContent.decode('utf-8')
+ for name, value in values.items():
+ pattern = "'%s': %%s,\n" % name
+ regex = re.compile(pattern % ".+")
+ replacement = pattern % value
+ if regex.search(updatedFileContent):
+ updatedFileContent = re.sub(regex, replacement, updatedFileContent)
+ else:
+ updatedFileContent = re.sub(r'( *)(.*hook.*)', r'\1%s\1\2' % replacement, updatedFileContent)
+
+ # Bundle is 'assets_common' as this route is only meant to update
+ # variables scss files
+ AssetsUtils.save_asset(url, 'web.assets_common', updatedFileContent, 'scss')
+ return True
+
@http.route(['/website/multi_render'], type='json', auth="public", website=True)
def multi_render(self, ids_or_xml_ids, values=None):
View = request.env['ir.ui.view']
diff --git a/addons/website/models/assets.py b/addons/website/models/assets.py
index 92aa9bb1d8a..0d2be3d2b91 100644
--- a/addons/website/models/assets.py
+++ b/addons/website/models/assets.py
@@ -1,38 +1,55 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo.addons.web_editor.controllers.main import Web_Editor
-from odoo.http import request
+
+from odoo import models
-class Web_Editor(Web_Editor):
+class Assets(models.AbstractModel):
+ _inherit = 'web_editor.assets'
- def _get_view_fields_to_read(self):
- res = super(Web_Editor, self)._get_view_fields_to_read()
+ def get_view_fields_to_read(self):
+ res = super(Assets, self).get_view_fields_to_read()
res.append('website_id')
return res
- def save_scss_or_js_view_hook(self):
- res = super(Web_Editor, self).save_scss_or_js_view_hook()
-
- website = request.env['website'].get_current_website()
- if website:
- res['website_id'] = website.id
- return res
-
- def save_scss_or_js_attachment_hook(self):
- res = super(Web_Editor, self).save_scss_or_js_attachment_hook()
-
- website = request.env['website'].get_current_website()
- if website:
- res['website_id'] = website.id
- return res
-
- def get_custom_attachment(self, custom_url, op='='):
- website = request.env['website'].get_current_website()
- res = super(Web_Editor, self).get_custom_attachment(custom_url, op=op)
+ def _get_custom_attachment(self, custom_url, op='='):
+ """
+ See web_editor.Assets._get_custom_attachment
+ Extend to only return the attachments related to the current website.
+ """
+ website = self.env['website'].get_current_website()
+ res = super(Assets, self)._get_custom_attachment(custom_url, op=op)
return res.with_context(website_id=website.id).filtered(lambda x: not x.website_id or x.website_id == website)
- def get_custom_view(self, custom_url, op='='):
- website = request.env['website'].get_current_website()
- res = super(Web_Editor, self).get_custom_view(custom_url, op=op)
+ def _get_custom_view(self, custom_url, op='='):
+ """
+ See web_editor.Assets._get_custom_view
+ Extend to only return the views related to the current website.
+ """
+ website = self.env['website'].get_current_website()
+ res = super(Assets, self)._get_custom_view(custom_url, op=op)
return res.with_context(website_id=website.id).filter_duplicate()
+
+ def _save_asset_attachment_hook(self):
+ """
+ See web_editor.Assets._save_asset_attachment_hook
+ Extend to add website ID at attachment creation.
+ """
+ res = super(Assets, self)._save_asset_attachment_hook()
+
+ website = self.env['website'].get_current_website()
+ if website:
+ res['website_id'] = website.id
+ return res
+
+ def _save_asset_view_hook(self):
+ """
+ See web_editor.Assets._save_asset_view_hook
+ Extend to add website ID at view creation.
+ """
+ res = super(Assets, self)._save_asset_view_hook()
+
+ website = self.env['website'].get_current_website()
+ if website:
+ res['website_id'] = website.id
+ return res
diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js
index 89bab6bb058..cc054b2fc00 100644
--- a/addons/website/static/src/js/widgets/theme.js
+++ b/addons/website/static/src/js/widgets/theme.js
@@ -416,46 +416,12 @@ var ThemeCustomizeDialog = Dialog.extend({
* @private
*/
_makeSCSSCusto: function (url, values) {
- var self = this;
- return this._rpc({ // TODO improve to be more efficient
- route: '/web_editor/get_assets_editor_resources',
+ return this._rpc({
+ route: '/website/make_scss_custo',
params: {
- 'key': 'website.layout',
- 'get_views': false,
- 'get_scss': true,
- 'get_js': false,
- 'bundles': false,
- 'bundles_restriction': [],
- 'only_user_custom_files': false,
+ 'url': url,
+ 'values': values,
},
- }).then(function (data) {
- var file = _.find(data.scss[0][1], function (file) {
- return file.url === url;
- });
-
- var updatedFileContent = file.arch;
- _.each(values, function (value, name) {
- var pattern = _.str.sprintf("'%s': %%s,\n", name);
- var regex = new RegExp(_.str.sprintf(pattern, ".+"));
- var replacement = _.str.sprintf(pattern, value);
- if (regex.test(updatedFileContent)) {
- updatedFileContent = updatedFileContent
- .replace(regex, replacement);
- } else {
- updatedFileContent = updatedFileContent
- .replace(/( *)(.*hook.*)/, _.str.sprintf('$1%s$1$2', replacement));
- }
- });
-
- return self._rpc({
- route: '/web_editor/save_scss_or_js',
- params: {
- 'url': file.url,
- 'bundle_xmlid': 'web.assets_common',
- 'content': updatedFileContent,
- 'file_type': 'scss',
- },
- });
});
},
/**