diff options
Diffstat (limited to 'addons/web_editor/controllers/main.py')
| -rw-r--r-- | addons/web_editor/controllers/main.py | 277 |
1 files changed, 103 insertions, 174 deletions
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() - - bundle_data[1][i] = dict( - url="/%s/%s" % (url_info["module"], url_info["resource_path"]), - arch=content, - customized=url_info["customized"], - ) + 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"], + } + return files_data_by_bundle - def save_scss_or_js_view_hook(self): - return {} - - def save_scss_or_js_attachment_hook(self): - return {} - - ## 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"] - - custom_url = self._make_custom_scss_or_js_file_url(url, bundle_xmlid) - - # 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) - - # 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', - } - - def views_linking_url(view): - """ - Returns whether the view arch has some html link 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)) - - 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': """ - <data inherit_id="%(inherit_xml_id)s" name="%(name)s"> - <xpath expr="//%%(tag)s[@%%(attribute)s='%(url_to_replace)s']" position="attributes"> - <attribute name="%%(attribute)s">%(new_url)s</attribute> - </xpath> - </data> - """ % { - '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 - } + @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. + + Params: + url (str): + the original url of the scss/js file which has to be modified + + bundle_xmlid (str): + the xmlid of the bundle in which the scss/js file addition can + be found + + content (str): the new content of the scss/js file + + file_type (str): 'scss' or 'js' + """ + request.env['web_editor.assets'].save_asset(url, bundle_xmlid, content, file_type) + + @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. + + Params: + url (str): + the original URL of the scss/js file to reset + + 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) |
