From 7ddf8e4d21d019c2d9d91922ae9be5ab2f54cdff Mon Sep 17 00:00:00 2001 From: Gorash Date: Fri, 18 Mar 2022 15:46:45 +0000 Subject: [PATCH] [IMP] base: modify the structure of generated methods This flat structure makes caching easier if odoo adds shared caches for example. In addition, the use of idempotent names makes it possible to compare methods during development. closes odoo/odoo#85110 Related: odoo/design-themes#554 Related: odoo/enterprise#24622 Signed-off-by: Martin Trigaux (mat) --- odoo/addons/base/models/ir_qweb.py | 75 ++++++++++++++++++------------ odoo/tools/profiler.py | 6 +-- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/odoo/addons/base/models/ir_qweb.py b/odoo/addons/base/models/ir_qweb.py index 314a288ded2..13ce3cb4841 100644 --- a/odoo/addons/base/models/ir_qweb.py +++ b/odoo/addons/base/models/ir_qweb.py @@ -434,7 +434,9 @@ ALLOWED_KEYWORD = frozenset(['False', 'None', 'True', 'and', 'as', 'elif', 'else # regexpr for string formatting and extract ( ruby-style )|( jinja-style ) used in `_compile_format` FORMAT_REGEX = re.compile(r'(?:#\{(.+?)\})|(?:\{\{(.+?)\}\})') RSTRIP_REGEXP = re.compile(r'\n[ \t]*$') +FISRT_RSTRIP_REGEXP = re.compile(r'^(\n[ \t]*)+(\n[ \t])') VARNAME_REGEXP = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*$') +TO_VARNAME_REGEXP = re.compile(r'[^A-Za-z0-9_]+') # Attribute name used outside the context of the QWeb. SPECIAL_DIRECTIVES = {'t-translation', 't-ignore', 't-title'} # Name of the variable to insert the content in t-call in the template. @@ -507,8 +509,6 @@ class IrQWeb(models.AbstractModel): _name = 'ir.qweb' _description = 'Qweb' - _name_gen = count() - @QwebTracker.wrap_render @api.model def _render(self, template, values=None, **options): @@ -582,7 +582,7 @@ class IrQWeb(models.AbstractModel): except (ValueError, UserError) as e: message = str(e) ClassError = e.__class__ - def not_found_template(self, values): + def not_found_template(self, values, gen0=None): if self.env.context.get('raise_if_not_found', True): raise ClassError(message) _logger.warning('Cannot load template %s: %s', template, message) @@ -615,19 +615,24 @@ class IrQWeb(models.AbstractModel): # generate code - def_name = f'template_{ref}' if isinstance(ref, int) else 'template' + compile_context['qweb_name_gen'] = count() + + def_name = self._make_name(TO_VARNAME_REGEXP.sub(r'_', f'template_{ref}'), compile_context) try: if element.text: - element.text = re.compile(r'^(\n[ \t]*)+(\n[ \t])').sub(r'\2', element.text) + element.text = FISRT_RSTRIP_REGEXP.sub(r'\2', element.text) + compile_context['_all_def'] = [] compile_context['_text_concat'] = [] self._append_text("", compile_context) # To ensure the template function is a generator and doesn't become a regular function code_lines = ( - [f'def {def_name}(self, values, log):'] + [f'def {def_name}(self, values, gen0, log):'] + self._compile_node(element, compile_context, 1) + self._flush_text(compile_context, 1, rstrip=True) ) + for lines in compile_context['_all_def']: + code_lines.extend(lines) except QWebException: raise except Exception as e: @@ -657,13 +662,15 @@ class IrQWeb(models.AbstractModel): # return the wrapped function - def render_template(self, values): + def render_template(self, values, gen0=None): try: if not values.get('xmlid'): - values['xmlid'] = compile_context['ref_name'] - values['viewid'] = compile_context['ref'] + values['xmlid'] = options['ref_name'] + values['viewid'] = options['ref'] log = {'last_path_node': None} - yield from compiled_fn(self, values, log) + if gen0 is None: + gen0 = [] + yield from compiled_fn(self, values, gen0, log) except (QWebException, TransactionRollbackError) as e: raise except Exception as e: @@ -857,9 +864,9 @@ class IrQWeb(models.AbstractModel): text_concat.clear() return [f"{' ' * level}yield {text!r}"] - def _make_name(self, prefix='var'): + def _make_name(self, prefix, compile_context): """Generates a unique name.""" - return f"{prefix}_{next(self._name_gen)}" + return f"{prefix}_{next(compile_context['qweb_name_gen'])}" def _is_static_node(self, el, compile_context): """ Test whether the given element is purely static, i.e. (there @@ -1517,17 +1524,19 @@ class IrQWeb(models.AbstractModel): code.append(indent_code(f"values.update({self._compile_expr(varname)})", level)) else: # set the content as value - def_name = self._make_name("qweb_t_set") content = ( - self._compile_directive(el, compile_context, 'inner-content', level + 1) + - self._flush_text(compile_context, level + 1)) + self._compile_directive(el, compile_context, 'inner-content', 1) + + self._flush_text(compile_context, 1)) if content: - code.append(indent_code(f"def {def_name}(self, values, log):", level)) - code.extend(content) - expr = f"Markup(''.join({def_name}(self, values, log)))" + def_name = self._make_name('t_set', compile_context) + compile_context['_all_def'].append([f"def {def_name}(self, values, gen0, log):"] + content) + expr = f"Markup(''.join({def_name}(self, values, gen0, log)))" else: expr = "''" - code.append(indent_code(f"values[{varname!r}] = {expr}", level)) + if varname == T_CALL_SLOT: + code.append(indent_code(f"gen0 = {expr}", level)) + else: + code.append(indent_code(f"values[{varname!r}] = {expr}", level)) for key in list(el.attrib): if key.startswith('t-set-') or key.startswith('t-setf-'): @@ -1732,9 +1741,9 @@ class IrQWeb(models.AbstractModel): self._compile_directives(el, compile_context, level + 1) + self._flush_text(compile_context, level + 1, rstrip=True)) - t_foreach = self._make_name('t_foreach') - size = self._make_name('size') - has_value = self._make_name('has_value') + t_foreach = self._make_name('t_foreach', compile_context) + size = self._make_name('size', compile_context) + has_value = self._make_name('has_value', compile_context) if expr_foreach.isdigit(): code.append(indent_code(f""" @@ -1839,7 +1848,7 @@ class IrQWeb(models.AbstractModel): if expr == T_CALL_SLOT and code_options != 'True': code.append(indent_code("if True:", level)) code.extend(tag_open) - code.append(indent_code("yield from values.get('0', [])", level + 1)) + code.append(indent_code("yield from gen0", level + 1)) code.extend(tag_close) return code elif ttype == 't-field': @@ -1857,7 +1866,7 @@ class IrQWeb(models.AbstractModel): force_display_dependent = True else: if expr == T_CALL_SLOT: - code.append(indent_code("content = Markup(''.join(values.get('0', [])))", level)) + code.append(indent_code("content = Markup(''.join(gen0))", level)) else: code.append(indent_code(f"content = {self._compile_expr(expr)}", level)) @@ -2007,14 +2016,18 @@ class IrQWeb(models.AbstractModel): code.append(indent_code(f"t_call_options.update(nsmap={{{', '.join(nsmap)}}})", level)) # values (t-out="0" from content and variables from t-set and t-set-*) - def_name = self._make_name("t_call_values") - code.append(indent_code(f"def {def_name}(self, values, log):", level)) - code.extend(self._compile_directive(el, compile_context, 'inner-content', level + 1)) - code.extend(self._compile_directive(el, compile_context, 'set', level + 1)) + def_name = self._make_name('t_call', compile_context) + + # values from content (t-out="0" and t-set inside the content) + code_content = [f"def {def_name}(self, values, gen0, log):"] + code_content.extend(self._compile_directive(el, compile_context, 'inner-content', 1)) + code_content.extend(self._compile_directive(el, compile_context, 'set', 1)) self._append_text('', compile_context) # To ensure the template function is a generator and doesn't become a regular function - code.extend(self._flush_text(compile_context, level + 1, rstrip=True)) + code_content.extend(self._flush_text(compile_context, 1, rstrip=True)) + compile_context['_all_def'].append(code_content) + code.append(indent_code("t_call_values = values.copy()", level)) - code.append(indent_code(f"t_call_values['0'] = Markup(''.join({def_name}(self, t_call_values, log)))", level)) + code.append(indent_code(f"t_call_gen0 = Markup(''.join({def_name}(self, t_call_values, gen0, log)))", level)) template = self._compile_format(expr) @@ -2022,7 +2035,7 @@ class IrQWeb(models.AbstractModel): code.append(indent_code(f""" irQweb = self.with_context(**t_call_options) render_template = irQweb._compile({template}) - yield from render_template(irQweb, t_call_values) + yield from render_template(irQweb, t_call_values, t_call_gen0) """, level)) return code diff --git a/odoo/tools/profiler.py b/odoo/tools/profiler.py index 08b02b011a9..2347f2003f1 100644 --- a/odoo/tools/profiler.py +++ b/odoo/tools/profiler.py @@ -297,15 +297,15 @@ class QwebTracker(): return method_compile(self, template) render_template = method_compile(self, template) - def profiled_method_compile(self, values): + def profiled_method_compile(self, values, gen0=None): ref = render_template.options.get('ref') ref_xml = render_template.options.get('ref_xml') qweb_tracker = QwebTracker(ref, ref_xml, self.env.cr) self = self.with_context(qweb_tracker=qweb_tracker) if qweb_tracker.execution_context_enabled: with ExecutionContext(template=ref): - return render_template(self, values) - return render_template(self, values) + return render_template(self, values, gen0) + return render_template(self, values, gen0) return profiled_method_compile return _tracked_compile