[IMP] IrQweb: merge Qweb engine file qweb.py and ir_qweb.py

QWeb is the primary templating engine used by Odoo. It is an XML
templating engine and used mostly to generate XML, HTML fragments and
pages.

To create new XML template, please see :doc:`QWeb Templates documentation
<https://www.odoo.com/documentation/15.0/developer/reference/frontend/qweb.html>`

In **input** you have an XML template giving the corresponding input
etree. Each etree input nodes are used to generate a python function.
This fonction is called and will give the XML **output**.
The ``_compile`` method is responsible to generate the function from the
etree, that function is a python generator that yield one output line at a
time. This generator is consumed by ``_render``. The generated function is
orm cached.

In the graphic below you can see theresume of the call of the methods
performed in the IrQweb class.

    Odoo
     ┗━► _render (returns MarkupSafe)
        ┗━► _compile (returns function)                                        ◄━━━━━━━━━┓
           ┗━► _compile_node (returns code string array)                       ◄━━━━━━━┓ ┃
              ┃  (add technical directives: t-inner-content, t-tag)                    ┃ ┃
              ┣━► _directives_eval_order (defined directive order)                     ┃ ┃
              ┃                                                                        ┃ ┃
              ┣━► _compile_directives                              (recursive) ◄━━━━┓  ┃ ┃
              ┃  ┣━► _compile_directive                                             ┃  ┃ ┃
              ┃  ┃    ┗━► t-if            ━━► _compile_directive_if                ━┫  ┃ ┃
              ┃  ┃    ┗━► t-foreach       ━━► _compile_directive_foreach           ━┫  ┃ ┃
              ┃  ┃    ┗━► t-*             ━━► ...                                  ━┛  ┃ ┃
              ┃  ┃    ┗━► t-inner-content ━━► _compile_directive_inner_content ◄━━━━┓ ━┛ ┃
              ┃  ┃    ┗━► t-tag           ━━► _compile_directive_tag               ━┫    ┃
              ┃  ┃    ┗━► t-call          ━━► _compile_directive_call              ━┫ ━━━┛
              ┃  ┃    ┗━► t-out           ━━► _compile_directive_out           ◄━┓ ━┫
              ┃  ┃    ┗━► t-field         ━━► _compile_directive_field          ━┛  ┃
              ┃  ┃                                                                  ┃
              ┗━━┻━► _compile_static_node                                          ━┛

Part-of: odoo/odoo#81024
This commit is contained in:
Gorash
2022-02-03 08:09:31 +00:00
parent 39ce6f02a6
commit 9ce5bc8881
13 changed files with 1476 additions and 1513 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
from odoo.tests import Form
from odoo.addons.hr.tests.common import TestHrCommon
from odoo.addons.base.models.qweb import QWebException
from odoo.addons.base.models.ir_qweb import QWebException
class TestMultiCompany(TestHrCommon):
+1 -1
View File
@@ -19,7 +19,7 @@ import odoo
from odoo import api, models, registry, exceptions, tools, http
from odoo.addons.base.models import ir_http
from odoo.addons.base.models.ir_http import RequestUID
from odoo.addons.base.models.qweb import QWebException
from odoo.addons.base.models.ir_qweb import QWebException
from odoo.http import request
from odoo.osv import expression
from odoo.tools import config, ustr, pycompat
+1
View File
@@ -47,6 +47,7 @@ from . import ir_http
from . import ir_model
from . import ir_model_fields
from . import ir_ui_view
from . import ir_qweb
from . import res_company
from . import res_config_settings
from . import res_partner
+19
View File
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from odoo import models
class QWebCodeFound(Exception):
"""
Exception raised when a qweb compilation encounter dynamic content if the
option `raise_on_code` is True.
"""
class IrQweb(models.AbstractModel):
_inherit = 'ir.qweb'
def _get_template_cache_keys(self):
return super()._get_template_cache_keys() + ['raise_on_code']
def _compile_directives(self, el, options, indent):
if options.get('raise_on_code'):
raise QWebCodeFound()
return super()._compile_directives(el, options, indent)
+5 -4
View File
@@ -10,8 +10,9 @@ from lxml import html
from markupsafe import Markup
from werkzeug import urls
from .ir_qweb import QWebCodeFound
from odoo import _, api, fields, models, tools
from odoo.addons.base.models.qweb import QWebCodeFound
from odoo.addons.base.models.ir_qweb import QWebException
from odoo.exceptions import UserError, AccessError
from odoo.tools import is_html_empty, safe_eval
from odoo.tools.rendering_tools import convert_inline_template_to_qweb, parse_inline_template, render_inline_template, template_env_globals
@@ -284,10 +285,10 @@ class MailRenderMixin(models.AbstractModel):
)
# remove the rendered tag <div> that was added in order to wrap potentially multiples nodes into one.
render_result = render_result[5:-6]
except QWebCodeFound:
group = self.env.ref('mail.group_mail_template_editor')
raise AccessError(_('Only users belonging to the "%s" group can modify dynamic templates.', group.name))
except Exception as e:
if isinstance(e, QWebException) and isinstance(e.error, QWebCodeFound):
group = self.env.ref('mail.group_mail_template_editor')
raise AccessError(_('Only users belonging to the "%s" group can modify dynamic templates.', group.name))
_logger.info("Failed to render template : %s", template_src, exc_info=True)
raise UserError(_("Failed to render QWeb template : %s)", e))
results[record.id] = render_result
+4 -4
View File
@@ -39,8 +39,8 @@ REMOTE_CONNECTION_TIMEOUT = 2.5
logger = logging.getLogger(__name__)
class QWeb(models.AbstractModel):
""" QWeb object for rendering editor stuff
class IrQWeb(models.AbstractModel):
""" IrQWeb object for rendering editor stuff
"""
_inherit = 'ir.qweb'
@@ -114,12 +114,12 @@ class QWeb(models.AbstractModel):
def _compile_directive_tag(self, el, options, indent):
if el.get('t-placeholder'):
el.set('t-att-placeholder', el.attrib.pop('t-placeholder'))
return super(QWeb, self)._compile_directive_tag(el, options, indent)
return super(IrQWeb, self)._compile_directive_tag(el, options, indent)
# order and ignore
def _directives_eval_order(self):
directives = super(QWeb, self)._directives_eval_order()
directives = super(IrQWeb, self)._directives_eval_order()
directives.insert(directives.index('call'), 'snippet')
directives.insert(directives.index('call'), 'snippet-call')
directives.insert(directives.index('call'), 'install')
+4 -4
View File
@@ -34,8 +34,8 @@ class AssetsBundleMultiWebsite(AssetsBundle):
extra = website_id_path + extra
return super(AssetsBundleMultiWebsite, self).get_debug_asset_url(extra, name, extension)
class QWeb(models.AbstractModel):
""" QWeb object for rendering stuff in the website context """
class IrQWeb(models.AbstractModel):
""" IrQWeb object for rendering stuff in the website context """
_inherit = 'ir.qweb'
@@ -47,14 +47,14 @@ class QWeb(models.AbstractModel):
'img': 'src',
}
def get_asset_bundle(self, xmlid, files, env=None, css=True, js=True):
def _get_asset_bundle(self, xmlid, files, env=None, css=True, js=True):
return AssetsBundleMultiWebsite(xmlid, files, env=env)
def _post_processing_att(self, tagName, atts, options):
if atts.get('data-no-post-process'):
return atts
atts = super(QWeb, self)._post_processing_att(tagName, atts, options)
atts = super(IrQWeb, self)._post_processing_att(tagName, atts, options)
if tagName == 'img' and 'loading' not in atts:
atts['loading'] = 'lazy' # default is auto
-1
View File
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import assetsbundle
from . import qweb
from . import ir_model
from . import ir_sequence
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15 -3
View File
@@ -12,7 +12,7 @@ from lxml.builder import E
from odoo.modules import get_module_resource
from odoo.tests.common import TransactionCase
from odoo.addons.base.models.qweb import QWebException
from odoo.addons.base.models.ir_qweb import QWebException
from odoo.tools import misc, mute_logger
from odoo.tools.json import scriptsafe as json_scriptsafe
@@ -697,7 +697,9 @@ class TestQWebBasic(TransactionCase):
("(lambda a: a + 5)(x)", {'x': 10}, 15),
("(lambda a: a + x)(5)", {'x': 10}, 15),
("sum(x for x in range(4)) + ((x))", {'x': 10}, 16),
("['test_' + x for x in ['a', 'b']]", {}, ['test_a', 'test_b'])
("['test_' + x for x in ['a', 'b']]", {}, ['test_a', 'test_b']),
("""1 and 2 and 0
or 9""", {}, 9),
]
IrQweb = self.env['ir.qweb']
@@ -882,6 +884,16 @@ class TestQWebBasic(TransactionCase):
rendered = self.env['ir.qweb']._render(t.id)
self.assertEqual(rendered.strip(), result.strip())
def test_out(self):
t = self.env['ir.ui.view'].create({
'name': 'test',
'type': 'qweb',
'arch_db': '''<t t-name="out-format"><div t-out="a">Default</div></t>'''
})
result = """<div>1</div>"""
rendered = self.env['ir.qweb']._render(t.id, {'a': 1})
self.assertEqual(rendered.strip(), result.strip())
def test_out_format_1(self):
t = self.env['ir.ui.view'].create({
'name': 'test',
@@ -1161,7 +1173,7 @@ class TestQWebStaticXml(TransactionCase):
return lambda: self.run_test_file(os.path.join(path, f))
@mute_logger('odoo.addons.base.models.qweb') # tests t-raw which is deprecated
@mute_logger('odoo.addons.base.models.ir_qweb') # tests t-raw which is deprecated
def run_test_file(self, path):
self.env.user.tz = 'Europe/Brussels'
doc = etree.parse(path).getroot()
@@ -21,7 +21,7 @@ from odoo.addons.base.models.ir_attachment import IrAttachment
from odoo.modules.module import get_resource_path, get_manifest
from odoo.tests import HttpCase, tagged
from odoo.tests.common import TransactionCase
from odoo.addons.base.models.qweb import QWebException
from odoo.addons.base.models.ir_qweb import QWebException
from odoo.tools import mute_logger, func
+1 -1
View File
@@ -313,7 +313,7 @@ class QwebTracker():
def wrap_compile_directive(cls, method_compile_directive):
@functools.wraps(method_compile_directive)
def _tracked_compile_directive(self, el, options, directive, indent):
if not options.get('profile') or directive in ('content', 'tag'):
if not options.get('profile') or directive in ('inner-content', 'tag'):
return method_compile_directive(self, el, options, directive, indent)
enter = self._indent(f"self.env.context['qweb_tracker'].enter_directive({directive!r}, {el.attrib!r}, {options['last_path_node']!r})", indent)