fix for Python 3.7

develop
Andreas Osim 2020-04-14 11:11:42 +02:00
parent 357c96c057
commit 7267c6aee6
4 changed files with 122 additions and 62 deletions

View File

@ -195,9 +195,11 @@ class AssetsBundle(object):
def clean_attachments(self, type): def clean_attachments(self, type):
""" Takes care of deleting any outdated ir.attachment records associated to a bundle before """ Takes care of deleting any outdated ir.attachment records associated to a bundle before
saving a fresh one. saving a fresh one.
When `type` is css we need to check that we are deleting a different version (and not *any* 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 version) because css may be paginated and, therefore, may produce multiple attachments for
the same bundle's version. the same bundle's version.
When `type` is js we need to check that we are deleting a different version (and not *any* 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 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 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) { (function (message) {
if (window.__assetsBundleErrorSeen) return; if (window.__assetsBundleErrorSeen) return;
window.__assetsBundleErrorSeen = true; window.__assetsBundleErrorSeen = true;
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
var alertTimeout = setTimeout(alert.bind(window, message), 0); var alertTimeout = setTimeout(alert.bind(window, message), 0);
if (typeof odoo === "undefined") return; if (typeof odoo === "undefined") return;
odoo.define("AssetsBundle.ErrorMessage", function (require) { odoo.define("AssetsBundle.ErrorMessage", function (require) {
"use strict"; "use strict";
var base = require("web_editor.base"); var base = require("web_editor.base");
var core = require("web.core"); var core = require("web.core");
var Dialog = require("web.Dialog"); var Dialog = require("web.Dialog");
var _t = core._t; var _t = core._t;
clearTimeout(alertTimeout); clearTimeout(alertTimeout);
base.ready().then(function () { base.ready().then(function () {
new Dialog(null, { new Dialog(null, {
title: _t("Style error"), title: _t("Style error"),
@ -722,4 +730,4 @@ class LessStylesheetAsset(PreprocessedCSS):
except IOError: except IOError:
lessc = 'lessc' lessc = 'lessc'
lesspath = get_resource_path('web', 'static', 'lib', 'bootstrap', 'less') lesspath = get_resource_path('web', 'static', 'lib', 'bootstrap', 'less')
return [lessc, '-', '--no-js', '--no-color', '--include-path=%s' % lesspath] return [lessc, '-', '--no-js', '--no-color', '--include-path=%s' % lesspath]

View File

@ -6,6 +6,8 @@ from io import BytesIO
from odoo import api, fields, models, _ from odoo import api, fields, models, _
from PIL import Image from PIL import Image
import babel import babel
from lxml import etree
from odoo.tools import html_escape as escape, posix_to_ldml, safe_eval, float_utils, format_date, pycompat from odoo.tools import html_escape as escape, posix_to_ldml, safe_eval, float_utils, format_date, pycompat
import logging import logging
@ -252,7 +254,17 @@ class HTMLConverter(models.AbstractModel):
@api.model @api.model
def value_to_html(self, value, options): 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("<body>%s</body>" % 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): class ImageConverter(models.AbstractModel):
@ -476,7 +488,7 @@ class Contact(models.AbstractModel):
'object': value, 'object': value,
'options': options '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): class QwebView(models.AbstractModel):

View File

@ -37,7 +37,9 @@ class IrQWeb(models.AbstractModel, QWeb):
@api.model @api.model
def render(self, id_or_xml_id, values=None, **options): def render(self, id_or_xml_id, values=None, **options):
""" render(id_or_xml_id, values, **options) """ render(id_or_xml_id, values, **options)
Render the template specified by the given name. Render the template specified by the given name.
:param id_or_xml_id: name or etree (see get_template) :param id_or_xml_id: name or etree (see get_template)
:param dict values: template values to be used for rendering :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) :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()) return ast.Name(id='False', ctx=ast.Load())
elif attr in ('true', '1'): elif attr in ('true', '1'):
return ast.Name(id='True', ctx=ast.Load()) return ast.Name(id='True', ctx=ast.Load())
return ast.Name(id=str(attr if attr is False else default), ctx=ast.Load()) return ast.Name(id=str(attr if attr is False else default), ctx=ast.Load())

View File

@ -5,7 +5,7 @@ import os.path
import re import re
import traceback import traceback
from collections import OrderedDict, Sized, Mapping, defaultdict from collections import OrderedDict, Sized, Mapping
from functools import reduce from functools import reduce
from itertools import tee, count from itertools import tee, count
from textwrap import dedent from textwrap import dedent
@ -308,8 +308,9 @@ class QWeb(object):
raise e raise e
except Exception as e: except Exception as e:
path = _options['last_path_node'] path = _options['last_path_node']
node = element.getroottree().xpath(path) element, document = self.get_template(template, options)
raise QWebException("Error when compiling AST", e, path, etree.tostring(node[0], encoding='unicode'), name) 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']) astmod.body.extend(_options['ast_calls'])
if 'profile' in options: if 'profile' in options:
@ -344,7 +345,7 @@ class QWeb(object):
except Exception as e: except Exception as e:
path = log['last_path_node'] path = log['last_path_node']
element, document = self.get_template(template, options) 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) raise QWebException("Error to render compiling AST", e, path, node and etree.tostring(node[0], encoding='unicode'), name)
return _compiled_fn return _compiled_fn
@ -694,6 +695,80 @@ class QWeb(object):
ctx=ctx 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 # order
def _directives_eval_order(self): def _directives_eval_order(self):
@ -732,7 +807,7 @@ class QWeb(object):
if not el.nsmap: if not el.nsmap:
unqualified_el_tag = el_tag = el.tag unqualified_el_tag = el_tag = el.tag
content = self._compile_directive_content(el, options) content = self._compile_directive_content(el, options)
attrib = el.attrib attrib = self._post_processing_att(el.tag, el.attrib, options)
else: else:
# Etree will remove the ns prefixes indirection by inlining the corresponding # Etree will remove the ns prefixes indirection by inlining the corresponding
# nsmap definition into the tag attribute. Restore the tag and prefix here. # nsmap definition into the tag attribute. Restore the tag and prefix here.
@ -762,6 +837,8 @@ class QWeb(object):
else: else:
attrib[key] = value attrib[key] = value
attrib = self._post_processing_att(el.tag, attrib, options)
# Update the dict of inherited namespaces before continuing the recursion. Note: # Update the dict of inherited namespaces before continuing the recursion. Note:
# since `options['nsmap']` is a dict (and therefore mutable) and we do **not** # 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 # 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: if attr_already_created:
# for name, value in t_attrs.items(): # tagName = $el.tag
# if value or isinstance(value, basestring)): body.append(ast.Assign(
# append(u' ') targets=[ast.Name(id='tagName', ctx=ast.Store())],
# append(name) value=ast.Str(el.tag))
# append(u'="') )
# append(escape(to_text((value))) body.extend(self._append_attributes())
# 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=[]
))
return body return body
@ -1512,6 +1542,14 @@ class QWeb(object):
atts = OrderedDict(atts) atts = OrderedDict(atts)
return 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): def _get_field(self, record, field_name, expression, tagName, field_options, options, values):
""" """
:returns: tuple: :returns: tuple: