From 7267c6aee6765bef0c32f900b72189bb3721b4bf Mon Sep 17 00:00:00 2001 From: Andreas Osim Date: Tue, 14 Apr 2020 11:11:42 +0200 Subject: [PATCH] fix for Python 3.7 --- .../addons/base/ir/ir_qweb/assetsbundle.py | 10 +- .../odoo/addons/base/ir/ir_qweb/fields.py | 16 +- .../odoo/addons/base/ir/ir_qweb/ir_qweb.py | 4 +- ext/odoo/odoo/addons/base/ir/ir_qweb/qweb.py | 154 +++++++++++------- 4 files changed, 122 insertions(+), 62 deletions(-) diff --git a/ext/odoo/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/ext/odoo/odoo/addons/base/ir/ir_qweb/assetsbundle.py index 059adda9..b0f2ef27 100644 --- a/ext/odoo/odoo/addons/base/ir/ir_qweb/assetsbundle.py +++ b/ext/odoo/odoo/addons/base/ir/ir_qweb/assetsbundle.py @@ -195,9 +195,11 @@ class AssetsBundle(object): def clean_attachments(self, type): """ Takes care of deleting any outdated ir.attachment records associated to a bundle before saving a fresh one. + When `type` is css we need to check that we are deleting a different version (and not *any* version) because css may be paginated and, therefore, may produce multiple attachments for the same bundle's version. + When `type` is js we need to check that we are deleting a different version (and not *any* version) because, as one of the creates in `save_attachment` can trigger a rollback, the call to `clean_attachments ` is made at the end of the method in order to avoid the rollback @@ -313,16 +315,22 @@ class AssetsBundle(object): (function (message) { if (window.__assetsBundleErrorSeen) return; window.__assetsBundleErrorSeen = true; + document.addEventListener("DOMContentLoaded", function () { var alertTimeout = setTimeout(alert.bind(window, message), 0); if (typeof odoo === "undefined") return; + odoo.define("AssetsBundle.ErrorMessage", function (require) { "use strict"; + var base = require("web_editor.base"); var core = require("web.core"); var Dialog = require("web.Dialog"); + var _t = core._t; + clearTimeout(alertTimeout); + base.ready().then(function () { new Dialog(null, { title: _t("Style error"), @@ -722,4 +730,4 @@ class LessStylesheetAsset(PreprocessedCSS): except IOError: lessc = 'lessc' lesspath = get_resource_path('web', 'static', 'lib', 'bootstrap', 'less') - return [lessc, '-', '--no-js', '--no-color', '--include-path=%s' % lesspath] \ No newline at end of file + return [lessc, '-', '--no-js', '--no-color', '--include-path=%s' % lesspath] diff --git a/ext/odoo/odoo/addons/base/ir/ir_qweb/fields.py b/ext/odoo/odoo/addons/base/ir/ir_qweb/fields.py index b71966e6..19e15ff4 100644 --- a/ext/odoo/odoo/addons/base/ir/ir_qweb/fields.py +++ b/ext/odoo/odoo/addons/base/ir/ir_qweb/fields.py @@ -6,6 +6,8 @@ from io import BytesIO from odoo import api, fields, models, _ from PIL import Image import babel +from lxml import etree + from odoo.tools import html_escape as escape, posix_to_ldml, safe_eval, float_utils, format_date, pycompat import logging @@ -252,7 +254,17 @@ class HTMLConverter(models.AbstractModel): @api.model def value_to_html(self, value, options): - return pycompat.to_text(value) + irQweb = self.env['ir.qweb'] + # wrap value inside a body and parse it as HTML + body = etree.fromstring("%s" % value, etree.HTMLParser(encoding='utf-8'))[0] + # use pos processing for all nodes with attributes + for element in body.iter(): + if element.attrib: + attrib = OrderedDict(element.attrib) + attrib = irQweb._post_processing_att(element.tag, attrib, options.get('template_options')) + element.attrib.clear() + element.attrib.update(attrib) + return etree.tostring(body, encoding='unicode', method='html')[6:-7] class ImageConverter(models.AbstractModel): @@ -476,7 +488,7 @@ class Contact(models.AbstractModel): 'object': value, 'options': options } - return self.env['ir.qweb'].render('base.contact', val) + return self.env['ir.qweb'].render('base.contact', val, **options.get('template_options')) class QwebView(models.AbstractModel): diff --git a/ext/odoo/odoo/addons/base/ir/ir_qweb/ir_qweb.py b/ext/odoo/odoo/addons/base/ir/ir_qweb/ir_qweb.py index 4b0e1633..7b612eb9 100644 --- a/ext/odoo/odoo/addons/base/ir/ir_qweb/ir_qweb.py +++ b/ext/odoo/odoo/addons/base/ir/ir_qweb/ir_qweb.py @@ -37,7 +37,9 @@ class IrQWeb(models.AbstractModel, QWeb): @api.model def render(self, id_or_xml_id, values=None, **options): """ render(id_or_xml_id, values, **options) + Render the template specified by the given name. + :param id_or_xml_id: name or etree (see get_template) :param dict values: template values to be used for rendering :param options: used to compile the template (the dict available for the rendering is frozen) @@ -426,4 +428,4 @@ class IrQWeb(models.AbstractModel, QWeb): return ast.Name(id='False', ctx=ast.Load()) elif attr in ('true', '1'): return ast.Name(id='True', ctx=ast.Load()) - return ast.Name(id=str(attr if attr is False else default), ctx=ast.Load()) \ No newline at end of file + return ast.Name(id=str(attr if attr is False else default), ctx=ast.Load()) diff --git a/ext/odoo/odoo/addons/base/ir/ir_qweb/qweb.py b/ext/odoo/odoo/addons/base/ir/ir_qweb/qweb.py index a773daf6..e12c89cd 100644 --- a/ext/odoo/odoo/addons/base/ir/ir_qweb/qweb.py +++ b/ext/odoo/odoo/addons/base/ir/ir_qweb/qweb.py @@ -5,7 +5,7 @@ import os.path import re import traceback -from collections import OrderedDict, Sized, Mapping, defaultdict +from collections import OrderedDict, Sized, Mapping from functools import reduce from itertools import tee, count from textwrap import dedent @@ -308,8 +308,9 @@ class QWeb(object): raise e except Exception as e: path = _options['last_path_node'] - node = element.getroottree().xpath(path) - raise QWebException("Error when compiling AST", e, path, etree.tostring(node[0], encoding='unicode'), name) + element, document = self.get_template(template, options) + node = element.getroottree().xpath(path) if ':' not in path else None + raise QWebException("Error when compiling AST", e, path, node and etree.tostring(node[0], encoding='unicode'), name) astmod.body.extend(_options['ast_calls']) if 'profile' in options: @@ -344,7 +345,7 @@ class QWeb(object): except Exception as e: path = log['last_path_node'] element, document = self.get_template(template, options) - node = element.getroottree().xpath(path) + node = element.getroottree().xpath(path) if ':' not in path else None raise QWebException("Error to render compiling AST", e, path, node and etree.tostring(node[0], encoding='unicode'), name) return _compiled_fn @@ -694,6 +695,80 @@ class QWeb(object): ctx=ctx ) + def _append_attributes(self): + # t_attrs = self._post_processing_att(tagName, t_attrs, options) + # for name, value in t_attrs.items(): + # if value or isinstance(value, string_types)): + # append(u' ') + # append(name) + # append(u'="') + # append(escape(pycompat.to_text((value))) + # append(u'"') + return [ + ast.Assign( + targets=[ast.Name(id='t_attrs', ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id='self', ctx=ast.Load()), + attr='_post_processing_att', + ctx=ast.Load() + ), + args=[ + ast.Name(id='tagName', ctx=ast.Load()), + ast.Name(id='t_attrs', ctx=ast.Load()), + ast.Name(id='options', ctx=ast.Load()), + ], keywords=[], + starargs=None, kwargs=None + ) + ), + ast.For( + target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()), + iter=ast.Call( + func=ast.Attribute( + value=ast.Name(id='t_attrs', ctx=ast.Load()), + attr='items', + ctx=ast.Load() + ), + args=[], keywords=[], + starargs=None, kwargs=None + ), + body=[ast.If( + test=ast.BoolOp( + op=ast.Or(), + values=[ + ast.Name(id='value', ctx=ast.Load()), + ast.Call( + func=ast.Name(id='isinstance', ctx=ast.Load()), + args=[ + ast.Name(id='value', ctx=ast.Load()), + ast.Name(id='string_types', ctx=ast.Load()) + ], + keywords=[], + starargs=None, kwargs=None + ) + ] + ), + body=[ + self._append(ast.Str(u' ')), + self._append(ast.Name(id='name', ctx=ast.Load())), + self._append(ast.Str(u'="')), + self._append(ast.Call( + func=ast.Name(id='escape', ctx=ast.Load()), + args=[ast.Call( + func=ast.Name(id='to_text', ctx=ast.Load()), + args=[ast.Name(id='value', ctx=ast.Load())], keywords=[], + starargs=None, kwargs=None + )], keywords=[], + starargs=None, kwargs=None + )), + self._append(ast.Str(u'"')), + ], + orelse=[] + )], + orelse=[] + ) + ] + # order def _directives_eval_order(self): @@ -732,7 +807,7 @@ class QWeb(object): if not el.nsmap: unqualified_el_tag = el_tag = el.tag content = self._compile_directive_content(el, options) - attrib = el.attrib + attrib = self._post_processing_att(el.tag, el.attrib, options) else: # Etree will remove the ns prefixes indirection by inlining the corresponding # nsmap definition into the tag attribute. Restore the tag and prefix here. @@ -762,6 +837,8 @@ class QWeb(object): else: attrib[key] = value + attrib = self._post_processing_att(el.tag, attrib, options) + # Update the dict of inherited namespaces before continuing the recursion. Note: # since `options['nsmap']` is a dict (and therefore mutable) and we do **not** # want changes done in deeper recursion to bevisible in earlier ones, we'll pass @@ -870,59 +947,12 @@ class QWeb(object): ))) if attr_already_created: - # for name, value in t_attrs.items(): - # if value or isinstance(value, basestring)): - # append(u' ') - # append(name) - # append(u'="') - # append(escape(to_text((value))) - # append(u'"') - body.append(ast.For( - target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()), - iter=ast.Call( - func=ast.Attribute( - value=ast.Name(id='t_attrs', ctx=ast.Load()), - attr='items', - ctx=ast.Load() - ), - args=[], keywords=[], - starargs=None, kwargs=None - ), - body=[ast.If( - test=ast.BoolOp( - op=ast.Or(), - values=[ - ast.Name(id='value', ctx=ast.Load()), - ast.Call( - func=ast.Name(id='isinstance', ctx=ast.Load()), - args=[ - ast.Name(id='value', ctx=ast.Load()), - ast.Name(id='string_types', ctx=ast.Load()) - ], - keywords=[], - starargs=None, kwargs=None - ) - ] - ), - body=[ - self._append(ast.Str(u' ')), - self._append(ast.Name(id='name', ctx=ast.Load())), - self._append(ast.Str(u'="')), - self._append(ast.Call( - func=ast.Name(id='escape', ctx=ast.Load()), - args=[ast.Call( - func=ast.Name(id='to_text', ctx=ast.Load()), - args=[ast.Name(id='value', ctx=ast.Load())], keywords=[], - starargs=None, kwargs=None - )], keywords=[], - starargs=None, kwargs=None - )), - self._append(ast.Str(u'"')), - ], - orelse=[] - )], - orelse=[] - )) + # tagName = $el.tag + body.append(ast.Assign( + targets=[ast.Name(id='tagName', ctx=ast.Store())], + value=ast.Str(el.tag)) + ) + body.extend(self._append_attributes()) return body @@ -1512,6 +1542,14 @@ class QWeb(object): atts = OrderedDict(atts) return atts + def _post_processing_att(self, tagName, atts, options): + """ Method called by the compiled code. This method may be overwrited + to filter or modify the attributes after they are compiled. + + @returns OrderedDict + """ + return atts + def _get_field(self, record, field_name, expression, tagName, field_options, options, values): """ :returns: tuple: