[IMP] base, website: small refactoring

Mainly to simplify website overrides and general api

closes odoo/odoo#121376

Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
This commit is contained in:
Xavier-Do
2023-06-10 11:14:12 +02:00
parent 1b9ac0e100
commit ca8dc2d9b4
11 changed files with 55 additions and 85 deletions
+1 -3
View File
@@ -64,9 +64,7 @@ class WebsocketController(Controller):
prevent new worker versions to be loaded from the browser cache.
"""
bundle = 'bus.websocket_worker_assets'
assets_params = request.env["ir.asset"]._get_assets_params()
files, _ = request.env["ir.qweb"]._get_asset_content(bundle, assets_params=assets_params)
asset = AssetsBundle(bundle, files, assets_params=assets_params)
asset = request.env["ir.qweb"]._get_asset_bundle(bundle)
stream = request.env['ir.binary']._get_stream_from(asset.js(
is_minified="assets" not in request.session.debug
))
+3 -6
View File
@@ -20,8 +20,7 @@ class LivechatController(http.Controller):
DOM of the livechat to avoid conflicts.
"""
bundle = 'im_livechat.assets_embed'
files, _ = request.env["ir.qweb"]._get_asset_content(bundle)
asset = AssetsBundle(bundle, files)
asset = request.env["ir.qweb"]._get_asset_bundle(bundle)
if ext == 'css':
raise request.not_found()
stream = request.env['ir.binary']._get_stream_from(asset.js())
@@ -30,8 +29,7 @@ class LivechatController(http.Controller):
@http.route('/im_livechat/assets_embed.<any(css, js):ext>', type='http', auth='public', cors='*')
def assets_embed(self, ext, **kwargs):
bundle = 'im_livechat.assets_embed'
files, _ = request.env["ir.qweb"]._get_asset_content(bundle)
asset = AssetsBundle(bundle, files)
asset = request.env["ir.qweb"]._get_asset_bundle(bundle)
if ext not in ('css', 'js'):
raise request.not_found()
stream = request.env['ir.binary']._get_stream_from(getattr(asset, ext)())
@@ -44,8 +42,7 @@ class LivechatController(http.Controller):
@http.route('/im_livechat/emoji_bundle', type='http', auth='public', cors='*')
def get_emoji_bundle(self):
bundle = 'mail.assets_emoji'
files, _ = request.env["ir.qweb"]._get_asset_content(bundle)
asset = AssetsBundle(bundle, files)
asset = request.env["ir.qweb"]._get_asset_bundle(bundle)
stream = request.env['ir.binary']._get_stream_from(asset.js())
return stream.get_response()
+1 -2
View File
@@ -36,8 +36,7 @@ class TestQweb(TransactionCaseWithUserDemo):
html = demo_env['ir.qweb']._render('test_website.test_template', {"user": demo}, website_id=website.id)
asset_bundle_xmlid = 'test_website.test_bundle'
qweb = self.env['ir.qweb']
files, _ = qweb._get_asset_content(asset_bundle_xmlid)
bundle = qweb._get_asset_bundle(asset_bundle_xmlid, files, env=self.env, css=True, js=True)
bundle = qweb._get_asset_bundle(asset_bundle_xmlid, css=True, js=True)
asset_version_js = bundle.get_version('js')
asset_version_css = bundle.get_version('css')
+1 -3
View File
@@ -607,9 +607,7 @@ class Web_Editor(http.Controller):
if re.match('^o-color-([1-5])$', css_color_value):
if not bundle_css:
bundle = 'web.assets_frontend'
files, _ = request.env["ir.qweb"]._get_asset_content(bundle)
assets_params = request.env["ir.asset"]._get_assets_params()
asset = AssetsBundle(bundle, files, assets_params=assets_params)
asset = request.env["ir.qweb"]._get_asset_bundle(bundle)
bundle_css = asset.css().index_content
color_search = re.search(r'(?i)--%s:\s+(%s|%s)' % (css_color_value, regex_hex, regex_rgba), bundle_css)
if not color_search:
+9 -2
View File
@@ -10,11 +10,18 @@ class IrAsset(models.Model):
key = fields.Char(copy=False) # used to resolve multiple assets in a multi-website environment
website_id = fields.Many2one('website', ondelete='cascade')
def _get_assets_params(self):
params = super()._get_assets_params()
def _get_asset_params(self):
params = super()._get_asset_params()
params['website_id'] = self.env['website'].get_current_website(fallback=False).id
return params
def _get_asset_extra(self, extra, website_id=None, **params):
extra = super()._get_asset_extra(extra, **params)
if extra == '%':
return extra
website_id_path = website_id and ('%s/' % website_id) or ''
return website_id_path + extra
def _get_related_assets(self, domain, website_id=None, **params):
if website_id:
domain += self.env['website'].website_domain(website_id)
-19
View File
@@ -18,22 +18,6 @@ _logger = logging.getLogger(__name__)
re_background_image = re.compile(r"(background-image\s*:\s*url\(\s*['\"]?\s*)([^)'\"]+)")
class AssetsBundleMultiWebsite(AssetsBundle):
def _get_asset_url_values(self, id, unique, extra, name, sep, extension):
if extra != '%':
website_id = self.assets_params.get('website_id')
website_id_path = website_id and ('%s/' % website_id) or ''
extra = website_id_path + extra
res = super(AssetsBundleMultiWebsite, self)._get_asset_url_values(id, unique, extra, name, sep, extension)
return res
def get_debug_asset_url(self, extra='', name='%', extension='%'):
if extra != '%':
website_id = self.assets_params.get('website_id')
website_id_path = website_id and ('%s/' % website_id) or ''
extra = website_id_path + extra
return super(AssetsBundleMultiWebsite, self).get_debug_asset_url(extra, name, extension)
class IrQWeb(models.AbstractModel):
""" IrQWeb object for rendering stuff in the website context """
@@ -108,9 +92,6 @@ class IrQWeb(models.AbstractModel):
return irQweb
def _get_asset_bundle(self, xmlid, files=None, env=None, css=True, js=True, debug_assets=False, rtl=False, assets_params=None):
return AssetsBundleMultiWebsite(xmlid, files, env=env, css=css, js=js, debug_assets=debug_assets, rtl=rtl, assets_params=assets_params)
def _post_processing_att(self, tagName, atts):
if atts.get('data-no-post-process'):
return atts
+4 -6
View File
@@ -243,15 +243,13 @@ class TestUi(odoo.tests.HttpCase):
'website_id': new_website.id,
})
files, _ = self.env['ir.qweb']._get_asset_content(asset_bundle_xmlid, assets_params={'website_id': website_default.id})
self.assertNotIn(custom_url, [f['url'] for f in files])
base_website_bundle = self.env['ir.qweb']._get_asset_bundle(asset_bundle_xmlid, files, env=self.env, assets_params={'website_id': website_default.id})
base_website_bundle = self.env['ir.qweb']._get_asset_bundle(asset_bundle_xmlid, assets_params={'website_id': website_default.id})
self.assertNotIn(custom_url, [f['url'] for f in base_website_bundle.files])
base_website_css_version = base_website_bundle.get_version('css')
base_website_js_version = base_website_bundle.get_version('js')
files, _ = self.env['ir.qweb']._get_asset_content('website.assets_wysiwyg', assets_params={'website_id': new_website.id})
self.assertIn(custom_url, [f['url'] for f in files])
new_website_bundle_modified = self.env['ir.qweb']._get_asset_bundle(asset_bundle_xmlid, files, env=self.env, assets_params={'website_id': new_website.id})
new_website_bundle_modified = self.env['ir.qweb']._get_asset_bundle('website.assets_wysiwyg', assets_params={'website_id': new_website.id})
self.assertIn(custom_url, [f['url'] for f in new_website_bundle_modified.files])
self.assertEqual(new_website_bundle_modified.get_version('css'), base_website_css_version)
self.assertNotEqual(new_website_bundle_modified.get_version('js'), base_website_js_version, "js version for new website should now have been changed")
+16 -30
View File
@@ -32,6 +32,7 @@ from odoo.modules.module import get_resource_path
from odoo.tools import (func, misc, transpile_javascript,
is_odoo_module, SourceMapGenerator, profiler,
apply_inheritance_specs)
from odoo.tools.constants import SCRIPT_EXTENSIONS, STYLE_EXTENSIONS
from odoo.tools.misc import file_open, html_escape as escape
from odoo.tools.pycompat import to_text
@@ -39,7 +40,6 @@ _logger = logging.getLogger(__name__)
EXTENSIONS = (".js", ".css", ".scss", ".sass", ".less", ".xml")
class CompileError(RuntimeError): pass
class AssetError(Exception):
@@ -57,7 +57,7 @@ class AssetsBundle(object):
TRACKED_BUNDLES = ['web.assets_common', 'web.assets_backend']
def __init__(self, name, files, env=None, css=True, js=True, debug_assets=False, rtl=False, assets_params=None):
def __init__(self, name, files, external_assets=(), env=None, css=True, js=True, debug_assets=False, rtl=False, assets_params=None):
"""
:param name: bundle name
:param files: files to be added to the bundle
@@ -77,6 +77,11 @@ class AssetsBundle(object):
self.has_js = js
self._checksum_cache = {}
self.is_debug_assets = debug_assets
self.external_assets = [
node
for node in external_assets
if (css and node[0].rpartition('.')[2] in STYLE_EXTENSIONS) or (js and node[0].rpartition('.')[2] in SCRIPT_EXTENSIONS)
]
# asset-wide html "media" attribute
for f in files:
@@ -139,7 +144,7 @@ class AssetsBundle(object):
src = self.get_debug_asset_url(name=js_attachment.name, extension='') if self.is_debug_assets else js_attachment[0].url
response.append((src, None))
return response
return self.external_assets + response
def get_version(self, asset_type):
return self.get_checksum(asset_type)[0:7]
@@ -162,26 +167,12 @@ class AssetsBundle(object):
self._checksum_cache[asset_type] = hashlib.sha512(unique_descriptor.encode()).hexdigest()[:64]
return self._checksum_cache[asset_type]
def _get_asset_template_url(self):
return "/web/assets/{id}-{unique}/{extra}{name}{sep}{extension}"
def _get_asset_url_values(self, id, unique, extra, name, sep, extension): # extra can contain direction or/and website
return {
'id': id,
'unique': unique,
'extra': extra,
'name': name,
'sep': sep,
'extension': extension,
'params': self.assets_params,
}
def get_asset_url(self, id='%', unique='%', extra='', name='%', sep="%", extension='%'):
return self._get_asset_template_url().format(
**self._get_asset_url_values(id=id, unique=unique, extra=extra, name=name, sep=sep, extension=extension)
)
def get_asset_url(self, attachment_id='%', unique='%', extra='', name='%', sep=".", extension='%'):
extra = self.env['ir.asset']._get_asset_extra(extra, **self.assets_params)
return f"/web/assets/{attachment_id}-{unique}/{extra}{name}{sep}{extension}"
def get_debug_asset_url(self, extra='', name='%', extension='%'):
extra = self.env['ir.asset']._get_asset_extra(extra, **self.assets_params)
return f"/web/assets/debug/{extra}{name}{extension}"
def _unlink_attachments(self, attachments):
@@ -213,13 +204,12 @@ class AssetsBundle(object):
url = self.get_asset_url(
extra='%s' % ('rtl/' if is_css and self.rtl else ''),
name=self.name,
sep='.',
extension=extension,
)
domain = [
('url', '=like', url),
'!', ('url', '=like', self.get_asset_url(unique=self.get_version('css' if is_css else 'js')))
'!', ('url', '=like', self.get_asset_url(unique=self.get_version('css' if is_css else 'js'), sep='%'))
]
attachments = ira.sudo().search(domain)
# avoid to invalidate cache if it's already empty (mainly useful for test)
@@ -251,7 +241,6 @@ class AssetsBundle(object):
unique=unique,
extra=extra, # not sure about css.map
name=self.name,
sep='.',
extension=extension,
)
query = """
@@ -273,8 +262,7 @@ class AssetsBundle(object):
unique=unique,
extra='%', #ignore website_id and rtl
name=self.name,
sep='.',
extension='%s' % extension,
extension=extension,
)
self.env.cr.execute(query, [SUPERUSER_ID, fallback_url_pattern])
@@ -295,11 +283,10 @@ class AssetsBundle(object):
self.add_post_rollback()
attachment = self.env['ir.attachment'].with_user(SUPERUSER_ID).create(values)
url = self.get_asset_url(
id=attachment.id,
attachment_id=attachment.id,
unique=unique,
extra=extra,
name=self.name,
sep='.',
extension=extension,
)
attachment.url = url
@@ -353,11 +340,10 @@ class AssetsBundle(object):
self.add_post_rollback()
attachment = ira.with_user(SUPERUSER_ID).create(values)
url = self.get_asset_url(
id=attachment.id,
attachment_id=attachment.id,
unique=self.get_version('css' if is_css else 'js'),
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.rtl else ''),
name=self.name,
sep='.',
extension=extension,
)
attachment.url = url
+10 -3
View File
@@ -90,13 +90,20 @@ class IrAsset(models.Model):
active = fields.Boolean(string='active', default=True)
sequence = fields.Integer(string="Sequence", default=DEFAULT_SEQUENCE, required=True)
def _get_assets_params(self):
def _get_asset_params(self):
"""
This method can be overriden to add param _get_asset_paths call.
Those params will be part of the orm cache key
"""
return {}
def _get_asset_extra(self, extra):
"""
This method can be overriden to use param as keyword arguments
Return the extra based on additionnal assets_params
"""
return extra
@tools.conditional(
'xml' not in tools.config['dev_mode'],
tools.ormcache('bundle', 'tuple(sorted(assets_params.items()))'),
@@ -120,7 +127,7 @@ class IrAsset(models.Model):
:param bundle: name of the bundle from which to fetch the file paths
:param assets_params: parameters needed by overrides, mainly website_id
see _get_assets_params
see _get_asset_params
:returns: the list of tuples (path, addon, bundle)
"""
installed = self._get_installed_addons_list()
@@ -242,7 +249,7 @@ class IrAsset(models.Model):
"""
installed = self._get_installed_addons_list()
target_path, _full_path, _modified = self._get_paths(target_path_def, installed)[0]
assets_params = self._get_assets_params()
assets_params = self._get_asset_params()
asset_paths = self._get_asset_paths(root_bundle, assets_params)
for path, _full_path, bundle, _modified in asset_paths:
+9 -9
View File
@@ -2426,7 +2426,7 @@ class IrQWeb(models.AbstractModel):
Else, the assets will be generated only once and then stored in cache.
"""
rtl = self.env['res.lang']._lang_get_direction(self.env.context.get('lang') or self.env.user.lang) == 'rtl'
assets_params = self.env['ir.asset']._get_assets_params() # website_id
assets_params = self.env['ir.asset']._get_asset_params() # website_id
debug_assets = debug and 'assets' in debug
if debug_assets:
@@ -2491,7 +2491,7 @@ class IrQWeb(models.AbstractModel):
def _get_asset_content(self, bundle, assets_params=None):
if assets_params is None:
assets_params = self.env['ir.asset']._get_assets_params() # website_id
assets_params = self.env['ir.asset']._get_asset_params() # website_id
asset_paths = self.env['ir.asset']._get_asset_paths(bundle=bundle, assets_params=assets_params)
files = []
external_asset = []
@@ -2507,8 +2507,11 @@ class IrQWeb(models.AbstractModel):
external_asset.append((path, None))
return (files, external_asset)
def _get_asset_bundle(self, bundle_name, files=None, env=None, css=True, js=True, debug_assets=False, rtl=False, assets_params=None):
return AssetsBundle(bundle_name, files, env=env, css=css, js=js, debug_assets=debug_assets, rtl=rtl, assets_params=assets_params)
def _get_asset_bundle(self, bundle_name, css=True, js=True, debug_assets=False, rtl=False, assets_params=None):
if assets_params is None:
assets_params = self.env['ir.asset']._get_asset_params()
files, external_assets = self._get_asset_content(bundle_name, assets_params)
return AssetsBundle(bundle_name, files, external_assets, env=self.env, css=css, js=js, debug_assets=debug_assets, rtl=rtl, assets_params=assets_params)
def _links_to_nodes(self, paths, defer_load=False, lazy_load=False, media=None):
return [self._link_to_node(path, defer_load=defer_load, lazy_load=lazy_load, media=media) for path in paths]
@@ -2564,11 +2567,8 @@ class IrQWeb(models.AbstractModel):
return ('script', attributes, None)
def _generate_asset_links(self, bundle, css=True, js=True, debug_assets=False, assets_params=None, rtl=False):
aggregable_files, external_assets = self._get_asset_content(bundle, assets_params=assets_params)
external_assets = [node for node in external_assets if (css and node[0].rpartition('.')[2] in STYLE_EXTENSIONS) or (js and node[0].rpartition('.')[2] in SCRIPT_EXTENSIONS)]
asset_bundle = self._get_asset_bundle(bundle, aggregable_files, env=self.env, css=css, js=js, debug_assets=debug_assets, rtl=rtl, assets_params=assets_params)
bundle_links = asset_bundle.get_links()
return external_assets + bundle_links
asset_bundle = self._get_asset_bundle(bundle, css=css, js=js, debug_assets=debug_assets, rtl=rtl, assets_params=assets_params)
return asset_bundle.get_links()
def _get_asset_link_urls(self, bundle, debug=False):
asset_nodes = self._get_asset_nodes(bundle, js=False, debug=debug)
@@ -742,8 +742,7 @@ class TestAssetsBundleWithIRAMock(FileTouchable):
def _get_asset(self):
with patch.object(type(self.env['ir.asset']), '_get_installed_addons_list', Mock(return_value=self.installed_modules)):
files, _ = self.env['ir.qweb']._get_asset_content(self.stylebundle_name)
return AssetsBundle(self.stylebundle_name, files, env=self.env, debug_assets=True)
return self.env['ir.qweb']._get_asset_bundle(self.stylebundle_name, debug_assets=True)
def _bundle(self, asset, should_create, should_unlink, reason=''):
self.counter.clear()