From 64bbef5bc1c78a0afb19167b9e6e8a9b969ccc52 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Mon, 11 Sep 2023 18:03:05 +0200 Subject: [PATCH] [IMP] base, web: generate assets outside rendering The generation inside the rendering has some drawbacks: - `commit_assetsbundle` is needed for reports rendering because the template rendering may generate some assets that will be accessed by another transaction before the transaction is committed. But this solution is not ideal since the transaction is committed in the middle of the request - when the first rendered page is a 404, the assets are not committed and the page is broken. - when starting, deleting an attachment can create a concurrent update error and the request is retried. This will occur once per attachment and for all worker trying to access the same resource. The whole transaction is rollbacked, even the previously created assets bundle. - The cold page load is a slower since there is more work to do. - Implementing a readonly request is difficult because it could be transformed to read write and re-executed if the assets bundle does not exist. Generating assets when needed solves those issues. The concurrency when deleting an assets could still occur but only once per bundle, and in a smaller transaction. This could be solved with a lock now that we have more control on the transaction. The commit_assetsbundle can be removed and 404 page should have a correct layout. The cold page load could be a little faster because the assets bundle can be generated in parallel requests instead of sequentially when rendering the page. Part-of: odoo/odoo#131353 --- addons/bus/controllers/websocket.py | 8 +- addons/bus/tests/test_assetsbundle.py | 9 +- addons/stock/report/stock_traceability.py | 2 - addons/test_website/tests/test_qweb.py | 12 +- addons/web/controllers/binary.py | 69 +++- addons/web/tests/test_assets.py | 12 +- addons/website/controllers/form.py | 1 - addons/website/models/ir_asset.py | 8 +- addons/website/tests/test_performance.py | 4 +- odoo/addons/base/models/assetsbundle.py | 25 +- odoo/addons/base/models/ir_actions_report.py | 11 - odoo/addons/base/models/ir_asset.py | 4 + odoo/addons/base/models/ir_qweb.py | 6 +- .../tests/test_assetsbundle.py | 332 +++++++----------- 14 files changed, 218 insertions(+), 285 deletions(-) diff --git a/addons/bus/controllers/websocket.py b/addons/bus/controllers/websocket.py index 6449d1ee69a..d870b3da29c 100644 --- a/addons/bus/controllers/websocket.py +++ b/addons/bus/controllers/websocket.py @@ -63,9 +63,7 @@ class WebsocketController(Controller): :param str v: Version of the worker, frontend only argument used to prevent new worker versions to be loaded from the browser cache. """ - bundle = 'bus.websocket_worker_assets' - 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 - )) + bundle_name = 'bus.websocket_worker_assets' + bundle = request.env["ir.qweb"]._get_asset_bundle(bundle_name, debug_assets="assets" in request.session.debug) + stream = request.env['ir.binary']._get_stream_from(bundle.js()) return stream.get_response() diff --git a/addons/bus/tests/test_assetsbundle.py b/addons/bus/tests/test_assetsbundle.py index 2c44b18da6b..0d539942923 100644 --- a/addons/bus/tests/test_assetsbundle.py +++ b/addons/bus/tests/test_assetsbundle.py @@ -14,9 +14,8 @@ class BusWebTests(odoo.tests.HttpCase): i.e. their hash has been recomputed and differ from the attachment's - The interface deals with those bus messages by displaying one notification """ - db_name = self.env.registry.db_name # start from a clean slate - self.env['ir.attachment'].search([('name', 'ilike', 'web.assets_web%')]).unlink() + self.env['ir.attachment'].search([('name', 'ilike', 'web.assets_%')]).unlink() self.env.registry.clear_cache() sendones = [] @@ -28,8 +27,10 @@ class BusWebTests(odoo.tests.HttpCase): self.patch(type(self.env['bus.bus']), '_sendone', patched_sendone) - self.authenticate('admin', 'admin') - self.url_open('/web') + self.url_open('/web/assets/-/web.assets_web.min.js') + self.url_open('/web/assets/ltr/web.assets_web.min.css') + self.url_open('/web/assets/-/web.assets_backend.min.js') + self.url_open('/web/assets/ltr/web.assets_backend.min.css') # One sendone for each asset bundle and for each CSS / JS self.assertEqual( diff --git a/addons/stock/report/stock_traceability.py b/addons/stock/report/stock_traceability.py index 009d8d25742..acede4805e9 100644 --- a/addons/stock/report/stock_traceability.py +++ b/addons/stock/report/stock_traceability.py @@ -211,8 +211,6 @@ class MrpStockReport(models.TransientModel): } context = dict(self.env.context) - if not config['test_enable']: - context['commit_assetsbundle'] = True if context.get('active_id') and context.get('active_model'): rcontext['reference'] = self.env[context.get('active_model')].browse(int(context.get('active_id'))).display_name diff --git a/addons/test_website/tests/test_qweb.py b/addons/test_website/tests/test_qweb.py index 31a2463d91f..610c8b945ed 100644 --- a/addons/test_website/tests/test_qweb.py +++ b/addons/test_website/tests/test_qweb.py @@ -35,22 +35,18 @@ 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'] - bundle = qweb._get_asset_bundle(asset_bundle_xmlid, css=True, js=True) + bundle = qweb._get_asset_bundle(asset_bundle_xmlid, css=True, js=True, assets_params={'website_id': website.id}) asset_version_js = bundle.get_version('js') asset_version_css = bundle.get_version('css') + css_url, js_url = bundle.get_links()[-2:] html = html.strip() html = re.sub(r'\?unique=[^"]+', '', html).encode('utf8') - css_attachement = demo_env['ir.attachment'].search([('url', '=like', f'/web/assets/{asset_version_css}/w-{website.id}_ltr/test_website.test_bundle.%')]) - js_attachement = demo_env['ir.attachment'].search([('url', '=like', f'/web/assets/{asset_version_js}/w-{website.id}_-/test_website.test_bundle.%')]) - self.assertEqual(len(css_attachement), 1) - self.assertEqual(len(js_attachement), 1) - format_data = { - "css": css_attachement.url, - "js": js_attachement.url, + "css": css_url, + "js": js_url, "user_id": demo.id, "filename": "Marc%20Demo", "alt": "Marc Demo", diff --git a/addons/web/controllers/binary.py b/addons/web/controllers/binary.py index 509e3947906..95e1cf2ce1a 100644 --- a/addons/web/controllers/binary.py +++ b/addons/web/controllers/binary.py @@ -8,6 +8,8 @@ import logging import os import unicodedata +import werkzeug.exceptions + try: from werkzeug.utils import send_file except ImportError: @@ -15,13 +17,12 @@ except ImportError: import odoo import odoo.modules.registry -from odoo import http, _ +from odoo import SUPERUSER_ID, _, http from odoo.exceptions import AccessError, UserError from odoo.http import request, Response from odoo.tools import file_open, file_path, replace_exceptions -from odoo.tools.mimetypes import guess_mimetype from odoo.tools.image import image_guess_size_from_field_name - +from odoo.tools.mimetypes import guess_mimetype _logger = logging.getLogger(__name__) @@ -83,26 +84,60 @@ class Binary(http.Controller): res.headers['Content-Security-Policy'] = "default-src 'none'" return res - @http.route(['/web/assets/debug//', + @http.route([ '/web/assets//', '/web/assets///'], type='http', auth="public") def content_assets(self, filename=None, unique='%', extra='-', nocache=False): - url = f'/web/assets/{unique}/{extra}/{filename}' - domain = [ - ('public', '=', True), - ('url', '!=', False), - ('url', '=like', url), - ('res_model', '=', 'ir.ui.view'), - ('res_id', '=', 0), - ] - attachment = request.env['ir.attachment'].sudo().search(domain, limit=1) + debug_assets = unique == 'debug' + attachment = None + if unique != 'debug': + url = f'/web/assets/{unique}/{extra}/{filename}' + domain = [ + ('public', '=', True), + ('url', '!=', False), + ('url', '=like', url), + ('res_model', '=', 'ir.ui.view'), + ('res_id', '=', 0), + ('create_uid', '=', SUPERUSER_ID), + ] + attachment = request.env['ir.attachment'].sudo().search(domain, limit=1) + if not attachment: + # try to generate one + with replace_exceptions(ValueError, by=werkzeug.exceptions.BadRequest): + extra_parts = extra.split('+') + params = request.env['ir.asset']._parse_assets_extra(extra_parts) + if debug_assets: + bundle_name, asset_type = filename.rsplit('.', 1) + else: + bundle_name, min_, asset_type = filename.rsplit('.', 2) + if min_ != 'min': + _logger.error("'min' expected in extension in non debug mode") + raise request.not_found() + css = asset_type == 'css' + js = asset_type == 'js' + if not js and not css: + _logger.error('Only js and css assets bundle are supported for now') + raise request.not_found() + bundle = request.env['ir.qweb']._get_asset_bundle( + bundle_name, + css=css, + js=js, + debug_assets=debug_assets, + rtl='rtl' in extra_parts, + assets_params=params, + ) + # check if the version matches. If not, redirect to the last version + if not debug_assets and unique != '%' and unique != bundle.get_version(asset_type): + return request.redirect(bundle.get_link(asset_type)) + if css and bundle.stylesheets: + attachment = bundle.css() + elif js and bundle.javascripts: + attachment = bundle.js() if not attachment: raise request.not_found() - with replace_exceptions(UserError, by=request.not_found()): - stream = request.env['ir.binary']._get_stream_from(attachment, 'raw', filename) - + stream = request.env['ir.binary']._get_stream_from(attachment, 'raw', filename) send_file_kwargs = {'as_attachment': False} - if unique: + if unique and unique != 'debug': send_file_kwargs['immutable'] = True send_file_kwargs['max_age'] = http.STATIC_CACHE_LONG if nocache: diff --git a/addons/web/tests/test_assets.py b/addons/web/tests/test_assets.py index 250e12aaa15..9cba020a5dd 100644 --- a/addons/web/tests/test_assets.py +++ b/addons/web/tests/test_assets.py @@ -27,17 +27,21 @@ class TestAssetsGenerateTimeCommon(odoo.tests.TransactionCase): for key in get_manifest(module).get('assets', []) } - for bundle in bundles: + for bundle_name in bundles: with mute_logger('odoo.addons.base.models.assetsbundle'): for assets_type in 'css', 'js': try: start_t = time.time() css = assets_type == 'css' js = assets_type == 'js' - self.env['ir.qweb']._generate_asset_links(bundle, css=css, js=js) - yield (f'{bundle}.{assets_type}', time.time() - start_t) + bundle = self.env['ir.qweb']._get_asset_bundle(bundle_name, css=css, js=js) + if assets_type == 'css' and bundle.stylesheets: + bundle.css() + if assets_type == 'js' and bundle.javascripts: + bundle.js() + yield (f'{bundle_name}.{assets_type}', time.time() - start_t) except ValueError: - _logger.info('Error detected while generating bundle %r %s', bundle, assets_type) + _logger.info('Error detected while generating bundle %r %s', bundle_name, assets_type) @odoo.tests.tagged('post_install', '-at_install', 'assets_bundle') diff --git a/addons/website/controllers/form.py b/addons/website/controllers/form.py index e1e24ab40d0..a2d1a00212b 100644 --- a/addons/website/controllers/form.py +++ b/addons/website/controllers/form.py @@ -231,7 +231,6 @@ class WebsiteForm(http.Controller): values.update({'reply_to': values.get('email_from'), 'email_from': email_from}) record = request.env[model_name].with_user(SUPERUSER_ID).with_context( mail_create_nosubscribe=True, - commit_assetsbundle=False, ).create(values) if custom or meta: diff --git a/addons/website/models/ir_asset.py b/addons/website/models/ir_asset.py index 231b6922680..90d8eec8cfe 100644 --- a/addons/website/models/ir_asset.py +++ b/addons/website/models/ir_asset.py @@ -9,7 +9,6 @@ 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_asset_params(self): params = super()._get_asset_params() params['website_id'] = self.env['website'].get_current_website(fallback=False).id @@ -22,6 +21,13 @@ class IrAsset(models.Model): website_id_path = website_id and ('website-%s+' % website_id) or '' return website_id_path + extra + def _parse_assets_extra(self, extra_parts): + params = super()._parse_assets_extra(extra_parts) + for extra_part in extra_parts: + if extra_part.startswith('w-'): + params['website_id'] = int(extra_part[2:]) + return params + def _get_related_assets(self, domain, website_id=None, **params): if website_id: domain += self.env['website'].website_domain(website_id) diff --git a/addons/website/tests/test_performance.py b/addons/website/tests/test_performance.py index bd4ffa1b3e8..676c9776497 100644 --- a/addons/website/tests/test_performance.py +++ b/addons/website/tests/test_performance.py @@ -278,8 +278,8 @@ class TestWebsitePerformancePost(UtilPerf): @mute_logger('odoo.http') def test_50_perf_sql_web_assets(self): # assets route /web/assets/.. - self.env['ir.qweb']._generate_asset_links('web.assets_frontend_lazy', css=False, js=True) - assets_url = self.env['ir.attachment'].search([('url', '=like', '/web/assets/%/web.assets_frontend_lazy%.js')], limit=1).url + assets_url = self.env['ir.qweb']._get_asset_bundle('web.assets_frontend_lazy', css=False, js=True).get_links()[0] + self.assertIn('web.assets_frontend_lazy.min.js', assets_url) select_tables_perf = { 'base_registry_signaling': 1, 'ir_attachment': 2, diff --git a/odoo/addons/base/models/assetsbundle.py b/odoo/addons/base/models/assetsbundle.py index 2c38d8d8fe9..74a52ead35b 100644 --- a/odoo/addons/base/models/assetsbundle.py +++ b/odoo/addons/base/models/assetsbundle.py @@ -116,11 +116,9 @@ class AssetsBundle(object): response = [] if self.has_css and self.stylesheets: - self.css(is_minified=not self.is_debug_assets) response.append(self.get_link('css')) if self.has_js and self.javascripts: - self.js(is_minified=not self.is_debug_assets) response.append(self.get_link('js')) return self.external_assets + response @@ -274,22 +272,11 @@ class AssetsBundle(object): 'raw': similar.raw, 'url': url, } - self.add_post_rollback() attachment = self.env['ir.attachment'].with_user(SUPERUSER_ID).create(values) attachment_id = attachment.id - if self.env.context.get('commit_assetsbundle') is True: - self.env.cr.commit() return self.env['ir.attachment'].sudo().browse(attachment_id) - def add_post_rollback(self): - """ - In some rare cases it is possible that an attachment is created during a transaction, added to the ormcache - but the transaction is rollbacked, leading to 404 when getting the attachments. - This postrollback hook will help fix this issue by clearing the cache if it isn't commited. - """ - self.env.cr.postrollback.add(self.env.registry._Registry__caches['assets'].clear) - def save_attachment(self, extension, content): """Record the given bundle in an ir.attachment and delete all other ir.attachments referring to this bundle (with the same name and extension). @@ -331,12 +318,8 @@ class AssetsBundle(object): 'raw': content.encode('utf8'), 'url': url, } - self.add_post_rollback() attachment = ira.with_user(SUPERUSER_ID).create(values) - if self.env.context.get('commit_assetsbundle') is True: - self.env.cr.commit() - self.clean_attachments(extension, url, extra) # For end-user assets (common and backend), send a message on the bus @@ -349,7 +332,8 @@ class AssetsBundle(object): return attachment - def js(self, is_minified=True): + def js(self): + is_minified = not self.is_debug_assets extension = 'min.js' if is_minified else 'js' js_attachment = self.get_attachments(extension) @@ -556,7 +540,8 @@ class AssetsBundle(object): # Returns the string by removing the tag. return etree.tostring(root, encoding='unicode')[6:-7] - def css(self, is_minified=True): + def css(self): + is_minified = not self.is_debug_assets extension = 'min.css' if is_minified else 'css' attachments = self.get_attachments(extension) if attachments: @@ -564,7 +549,7 @@ class AssetsBundle(object): css = self.preprocess_css() if self.css_errors: - error_message = '\n'.join(self.css_errors).replace('"', '\\"').replace('\n', '\A').replace('*', '\*') + error_message = '\n'.join(self.css_errors).replace('"', r'\\"').replace('\n', r'\A').replace('*', r'\*') previous_attachment = self.get_attachments(extension, ignore_version=True) previous_css = previous_attachment.raw.decode() if previous_attachment else '' css_error_message_header = '\n\n/* ## CSS error message ##*/' diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index 258b0ada3fe..a9016156545 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -724,17 +724,6 @@ class IrActionsReport(models.Model): # https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2083 additional_context = {'debug': False} - # As the assets are generated during the same transaction as the rendering of the - # templates calling them, there is a scenario where the assets are unreachable: when - # you make a request to read the assets while the transaction creating them is not done. - # Indeed, when you make an asset request, the controller has to read the `ir.attachment` - # table. - # This scenario happens when you want to print a PDF report for the first time, as the - # assets are not in cache and must be generated. To workaround this issue, we manually - # commit the writes in the `ir.attachment` table. It is done thanks to a key in the context. - if not config['test_enable'] and 'commit_assetsbundle' not in self.env.context: - additional_context['commit_assetsbundle'] = True - html = self.with_context(**additional_context)._render_qweb_html(report_ref, res_ids_wo_stream, data=data)[0] bodies, html_ids, header, footer, specific_paperformat_args = self.with_context(**additional_context)._prepare_html(html, report_model=report_sudo.model) diff --git a/odoo/addons/base/models/ir_asset.py b/odoo/addons/base/models/ir_asset.py index bc363e859b7..5daa171a9c8 100644 --- a/odoo/addons/base/models/ir_asset.py +++ b/odoo/addons/base/models/ir_asset.py @@ -105,6 +105,10 @@ class IrAsset(models.Model): """ return extra + + def _parse_assets_extra(self, extra_parts): + return {} + @tools.conditional( 'xml' not in tools.config['dev_mode'], tools.ormcache('bundle', 'tuple(sorted(assets_params.items()))', cache='assets'), diff --git a/odoo/addons/base/models/ir_qweb.py b/odoo/addons/base/models/ir_qweb.py index 04dbe8fc17e..651770d8da3 100644 --- a/odoo/addons/base/models/ir_qweb.py +++ b/odoo/addons/base/models/ir_qweb.py @@ -2625,11 +2625,11 @@ class IrQWeb(models.AbstractModel): links = [] start = time.time() for bundle in sorted(js_bundles): - links += self._generate_asset_links(bundle, css=False, js=True) + links += self._get_asset_bundle(bundle, css=False, js=True).js() _logger.info('JS Assets bundles generated in %s seconds', time.time()-start) start = time.time() for bundle in sorted(css_bundles): - links += self._generate_asset_links(bundle, css=True, js=False) + links += self._get_asset_bundle(bundle, css=True, js=False).css() _logger.info('CSS Assets bundles generated in %s seconds', time.time()-start) return links @@ -2650,10 +2650,8 @@ class IrQWeb(models.AbstractModel): js_bundles.add(asset) if css: css_bundles.add(asset) - return (js_bundles, css_bundles) - def render(template_name, values, load, **options): """ Rendering of a qweb template without database and outside the registry. (Widget, field, or asset rendering is not implemented.) diff --git a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py index 66aae287584..72b4256856c 100644 --- a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py +++ b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py @@ -123,25 +123,6 @@ class FileTouchable(AddonManifestPatched): self.touches[filepath] = touch_time or time.time() return patch('os.path.getmtime', lambda filename: self.touches.get(filename) or GETMTINE(filename)) -@tagged('-at_install', 'post_install') -class Test404(HttpCase): - def get_assets_node_cache(self): - return [e for e in self.env.registry._Registry__caches['assets'].d if e[0] == 'ir.qweb' and '_generate_' in str(e[1])] #_generate_asset_nodes_cache - - def test_rollback(self): - """ - In some rare case, attachments can be generatted, added to the cache but the transaction is rollbacked leading to 404 - this actually tests the add_post_rollback behaviour - """ - self.env['ir.attachment'].search([('url', '=like', '/web/assets/%web.assets_frontend%')]).unlink() - self.env.registry.clear_cache() - self.url_open('/some_404_routes') - self.assertFalse(self.get_assets_node_cache(), 'orm_cache should be emptied by rollback') - self.assertFalse(self.env['ir.attachment'].search([('url', '=like', '/web/assets/%web.assets_frontend%%')]), "attachments should have been rollbacked") - self.url_open('/') - self.assertTrue(self.env['ir.attachment'].search([('url', '=like', '/web/assets/%web.assets_frontend%%')]), "attachments should have been generated as expected") - self.assertTrue(self.get_assets_node_cache(), 'orm_cache should remain populated') - self.env.registry.clear_cache() class TestJavascriptAssetsBundle(FileTouchable): @classmethod @@ -175,7 +156,7 @@ class TestJavascriptAssetsBundle(FileTouchable): """ Checks that a bundle creates an ir.attachment record when its `js` method is called for the first time and this ir.attachment is different depending on `is_minified` param. """ - self.bundle = self._get_asset(self.jsbundle_name) + self.bundle = self._get_asset(self.jsbundle_name, debug_assets=False) # there shouldn't be any minified attachment associated to this bundle self.assertEqual(len(self._any_ira_for_bundle('min.js')), 0, @@ -199,7 +180,8 @@ class TestJavascriptAssetsBundle(FileTouchable): "there shouldn't be any non-minified attachment associated to this bundle") # trigger the first generation and, thus, the first save in database for the non-minified version. - self.bundle.js(is_minified=False) + self.bundle_debug = self._get_asset(self.jsbundle_name, debug_assets=True) + self.bundle_debug.js() # there should be one non-minified attachment associated to this bundle self.assertEqual(len(self._any_ira_for_bundle('js')), 1, @@ -211,7 +193,7 @@ class TestJavascriptAssetsBundle(FileTouchable): """ Checks that the bundle's cache is working, i.e. that the bundle creates only one ir.attachment record when rendered multiple times. """ - bundle0 = self._get_asset(self.jsbundle_name) + bundle0 = self._get_asset(self.jsbundle_name, debug_assets=False) bundle0.js() self.assertEqual(len(self._any_ira_for_bundle('min.js')), 1, @@ -221,7 +203,7 @@ class TestJavascriptAssetsBundle(FileTouchable): ira0 = self._any_ira_for_bundle('min.js') date0 = ira0.create_date - bundle1 = self._get_asset(self.jsbundle_name) + bundle1 = self._get_asset(self.jsbundle_name, debug_assets=False) bundle1.js() self.assertEqual(len(self._any_ira_for_bundle('min.js')), 1, @@ -257,7 +239,7 @@ class TestJavascriptAssetsBundle(FileTouchable): "the version must should because the bundle has changed.") # check if the previous attachment is correctly cleaned - self.assertEqual(len(self._any_ira_for_bundle('min.js')), 1, + self.assertEqual(len(self._any_ira_for_bundle('js')), 1, "there should be one minified attachment associated to this bundle") def test_04_content_invalidation(self): @@ -298,6 +280,7 @@ class TestJavascriptAssetsBundle(FileTouchable): """ debug_bundle = self._get_asset(self.jsbundle_name) content = debug_bundle.get_links() + debug_bundle.js() # there should be a minified file self.assertIn('test_assetsbundle.bundle1.min.js', content[0]) @@ -315,6 +298,7 @@ class TestJavascriptAssetsBundle(FileTouchable): """ debug_bundle = self._get_asset(self.jsbundle_name, debug_assets=True) content = debug_bundle.get_links() + debug_bundle.js() # there should be a minified file self.assertIn('test_assetsbundle.bundle1.js', content[0], "there should be one non-minified assets created in debug assets mode") @@ -390,11 +374,12 @@ class TestJavascriptAssetsBundle(FileTouchable): """ Check that a bundle in debug mode outputs non-minified assets. """ debug_bundle = self._get_asset(self.cssbundle_name, debug_assets=True) - content = debug_bundle.get_links() + links = debug_bundle.get_links() # there should be a minified file - self.assertEqual(content[0], '/web/assets/debug/ltr/test_assetsbundle.bundle2.css') + self.assertEqual(links[0], '/web/assets/debug/ltr/test_assetsbundle.bundle2.css') # there should be one css asset created in debug mode + debug_bundle.css() self.assertEqual(len(self._any_ira_for_bundle('css')), 1, 'there should be one css asset created in debug mode') @@ -441,47 +426,43 @@ class TestJavascriptAssetsBundle(FileTouchable): one for ltr and one for rtl. """ # Assets access for en_US language - ltr_bundle0 = self._get_asset(self.cssbundle_name) + ltr_bundle0 = self._get_asset(self.cssbundle_name, debug_assets=False) ltr_bundle0.css() self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) ltr_version0 = ltr_bundle0.get_version('css') ltr_ira0 = self._any_ira_for_bundle('min.css') - ltr_date0 = ltr_ira0.create_date + self.assertTrue(ltr_ira0) - ltr_bundle1 = self._get_asset(self.cssbundle_name) + ltr_bundle1 = self._get_asset(self.cssbundle_name, debug_assets=False) ltr_bundle1.css() self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1) ltr_version1 = ltr_bundle1.get_version('css') ltr_ira1 = self._any_ira_for_bundle('min.css') - ltr_date1 = ltr_ira1.create_date + self.assertTrue(ltr_ira1) self.assertEqual(ltr_version0, ltr_version1) - self.assertEqual(ltr_date0, ltr_date1) - rtl_bundle0 = self._get_asset(self.cssbundle_name, rtl=True) + rtl_bundle0 = self._get_asset(self.cssbundle_name, rtl=True, debug_assets=False) rtl_bundle0.css() self.assertEqual(len(self._any_ira_for_bundle('min.css', rtl=True)), 1) rtl_version0 = rtl_bundle0.get_version('css') - rtl_ira0 = self._any_ira_for_bundle('min.css', rtl=True) - rtl_date0 = rtl_ira0.create_date + self._any_ira_for_bundle('min.css', rtl=True) - rtl_bundle1 = self._get_asset(self.cssbundle_name, rtl=True) + rtl_bundle1 = self._get_asset(self.cssbundle_name, rtl=True, debug_assets=False) rtl_bundle1.css() self.assertEqual(len(self._any_ira_for_bundle('min.css', rtl=True)), 1) rtl_version1 = rtl_bundle1.get_version('css') rtl_ira1 = self._any_ira_for_bundle('min.css', rtl=True) - rtl_date1 = rtl_ira1.create_date self.assertEqual(rtl_version0, rtl_version1) - self.assertEqual(rtl_date0, rtl_date1) # Checks rtl and ltr bundles are different self.assertNotEqual(ltr_ira1.id, rtl_ira1.id) @@ -514,7 +495,7 @@ class TestJavascriptAssetsBundle(FileTouchable): ltr_bundle1.css() ltr_last_modified1 = ltr_bundle1.get_checksum('css') ltr_version1 = ltr_bundle1.get_version('css') - ltr_ira1 = self._any_ira_for_bundle('min.css') + ltr_ira1 = self._any_ira_for_bundle('css') self.assertNotEqual(ltr_last_modified0, ltr_last_modified1) self.assertNotEqual(ltr_version0, ltr_version1) @@ -523,7 +504,7 @@ class TestJavascriptAssetsBundle(FileTouchable): rtl_bundle1.css() rtl_last_modified1 = rtl_bundle1.get_checksum('css') rtl_version1 = rtl_bundle1.get_version('css') - rtl_ira1 = self._any_ira_for_bundle('min.css', rtl=True) + rtl_ira1 = self._any_ira_for_bundle('css', rtl=True) self.assertNotEqual(rtl_last_modified0, rtl_last_modified1) self.assertNotEqual(rtl_version0, rtl_version1) @@ -532,7 +513,7 @@ class TestJavascriptAssetsBundle(FileTouchable): # check if the previous attachment is correctly cleaned css_bundles = self.env['ir.attachment'].search([ - ('url', '=like', f'/web/assets/%/{self.cssbundle_name}%.min.css'), + ('url', '=like', f'/web/assets/%/{self.cssbundle_name}%.css'), ]) self.assertEqual(len(css_bundles), 2) @@ -599,6 +580,7 @@ class TestJavascriptAssetsBundle(FileTouchable): self.assertEqual(f'/web/assets/debug/rtl/{self.cssbundle_name}.css', content[0], "there should be an css assets bundle in /debug/rtl if user's lang direction is rtl and debug=assets") + debug_bundle.css() # there should be an css assets bundle created in /rtl if user's lang direction is rtl and debug=assets css_bundle = self.env['ir.attachment'].search([ ('url', '=like', f'/web/assets/%/rtl/{self.cssbundle_name}.css'), @@ -608,51 +590,42 @@ class TestJavascriptAssetsBundle(FileTouchable): def test_20_external_lib_assets(self): html = self.env['ir.ui.view']._render_template('test_assetsbundle.template2') - attachments = self.env['ir.attachment'].search([('url', '=like', '/web/assets/%/test_assetsbundle.bundle4.%')]) - self.assertEqual(len(attachments), 2) - format_data = { - "js": attachments[0].url, - "css": attachments[1].url, - } - self.assertEqual(str(html.strip()), (""" + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + links = bundle.get_links() + self.assertEqual(len(links), 6) + + self.assertEqual(str(html.strip()), (f""" - + - + -""" % format_data)) +""")) def test_21_external_lib_assets_debug_mode(self): html = self.env['ir.ui.view']._render_template('test_assetsbundle.template2', {"debug": "assets"}) - attachments = self.env['ir.attachment'].search([('url', '=like', '%/test_assetsbundle.bundle4.js')]) - self.assertEqual(len(attachments), 1) - - format_data = { - "css": '/web/assets/debug/ltr/test_assetsbundle.bundle4.css', - "js": '/web/assets/debug/-/test_assetsbundle.bundle4.js', - } self.assertEqual(str(html.strip()), (""" - + - + -""" % format_data)) +""")) @tagged('-at_install', 'post_install') @@ -743,13 +716,13 @@ class TestAssetsBundleWithIRAMock(FileTouchable): self.patch(IrAttachment, 'create', create) self.patch(AssetsBundle, '_unlink_attachments', unlink) - def _get_asset(self): + def _get_asset(self, debug_assets=True): with patch.object(type(self.env['ir.asset']), '_get_installed_addons_list', Mock(return_value=self.installed_modules)): - return self.env['ir.qweb']._get_asset_bundle(self.stylebundle_name, debug_assets=True) + return self.env['ir.qweb']._get_asset_bundle(self.stylebundle_name, debug_assets=debug_assets) - def _bundle(self, asset, should_create, should_unlink, reason=''): + def _bundle(self, bundle, should_create, should_unlink, reason=''): self.counter.clear() - asset.get_links() + bundle.css() if should_create: self.assertEqual(self.counter['create'], 2, f'An attachment should have been created {reason}') else: @@ -824,9 +797,8 @@ class TestAssetsManifest(AddonManifestPatched): self.assertEqual(tested, reference) def test_01_globmanifest(self): - view = self.make_asset_view('test_assetsbundle.manifest1') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest1.min.js')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest1') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -846,9 +818,8 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_02_globmanifest_no_duplicates(self): - view = self.make_asset_view('test_assetsbundle.manifest2') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest2.min.js')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest2') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -868,9 +839,8 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_03_globmanifest_file_before(self): - view = self.make_asset_view('test_assetsbundle.manifest3') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest3.min.js')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest3') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -890,14 +860,13 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_04_globmanifest_with_irasset(self): - view = self.make_asset_view('test_assetsbundle.manifest4') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'bundle': 'test_assetsbundle.manifest4', 'path': 'test_assetsbundle/static/src/js/test_jsfile1.js', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4.min.js')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -911,14 +880,14 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_05_only_irasset(self): - view = self.make_asset_view('test_assetsbundle.irasset1') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'bundle': 'test_assetsbundle.irasset1', 'path': 'test_assetsbundle/static/src/js/test_jsfile1.js', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irasset1.min.js')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irasset1') + attach = bundle.js() + content = attach.raw.decode() self.assertStringEqual( content, @@ -928,8 +897,7 @@ class TestAssetsManifest(AddonManifestPatched): ''' ) - def test_06_replace(self): - view = self.make_asset_view('test_assetsbundle.manifest1') + def test_06_1_replace(self): self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'bundle': 'test_assetsbundle.manifest1', @@ -937,13 +905,12 @@ class TestAssetsManifest(AddonManifestPatched): 'target': 'test_assetsbundle/static/src/js/test_jsfile1.js', 'path': 'http://external.link/external.js', }) - rendered = self.env['ir.qweb']._render(view.id) - html_tree = lxml.etree.fromstring(rendered) - scripts = html_tree.findall('script') + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest1') + scripts = [link for link in bundle.get_links() if link.endswith('js')] self.assertEqual(len(scripts), 2) - self.assertEqual(scripts[0].get('src'), 'http://external.link/external.js') - - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest1')], order='create_date DESC', limit=1) + self.assertEqual(scripts[0], 'http://external.link/external.js') + attach = bundle.js() + self.assertEqual(scripts[1], attach.url) content = attach.raw.decode() self.assertStringEqual( content, @@ -960,7 +927,6 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_06_2_replace(self): - view = self.make_asset_view('test_assetsbundle.manifest4') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'bundle': 'test_assetsbundle.manifest4', @@ -968,7 +934,8 @@ class TestAssetsManifest(AddonManifestPatched): 'path': 'test_assetsbundle/static/src/js/test_jsfile1.js', 'target': 'test_assetsbundle/static/src/js/test_jsfile3.js', }) - self.env['ir.qweb']._render(view.id) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) content = attach.raw.decode() self.assertStringEqual( @@ -980,7 +947,6 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_06_3_replace_globs(self): - view = self.make_asset_view('test_assetsbundle.manifest4') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'directive': 'prepend', @@ -997,8 +963,8 @@ class TestAssetsManifest(AddonManifestPatched): }) # asset is now: js_file1 ; js_file2 ; js_file3 # because js_file is replaced by 1 and 2 - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1015,15 +981,14 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_07_remove(self): - view = self.make_asset_view('test_assetsbundle.manifest5') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'bundle': 'test_assetsbundle.manifest5', 'directive': 'remove', 'path': 'test_assetsbundle/static/src/js/test_jsfile2.js', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest5')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest5') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1046,7 +1011,6 @@ class TestAssetsManifest(AddonManifestPatched): 'path': '/test_assetsbundle/static/src/js/test_jsfile1.js', }) - view = self.make_asset_view('test_assetsbundle.remove_error') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'bundle': 'test_assetsbundle.remove_error', @@ -1054,34 +1018,33 @@ class TestAssetsManifest(AddonManifestPatched): 'path': 'test_assetsbundle/static/src/js/test_doesntexist.js', }) with self.assertRaises(Exception) as cm: - self.env['ir.qweb']._render(view.id) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.remove_error') + bundle.js() self.assertTrue( "['test_assetsbundle/static/src/js/test_doesntexist.js'] not found" in str(cm.exception) ) def test_09_remove_wholeglob(self): - view = self.make_asset_view('test_assetsbundle.manifest2') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'bundle': 'test_assetsbundle.manifest2', 'directive': 'remove', 'path': 'test_assetsbundle/static/src/*/**', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest2.js')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest2') # indeed everything in the bundle matches the glob, so there is no attachment - self.assertFalse(attach) + self.assertFalse(bundle.javascripts) + self.assertFalse(bundle.get_links()) def test_10_prepend(self): - view = self.make_asset_view('test_assetsbundle.manifest4') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'directive': 'prepend', 'bundle': 'test_assetsbundle.manifest4', 'path': 'test_assetsbundle/static/src/js/test_jsfile1.js', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1095,15 +1058,14 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_11_include(self): - view = self.make_asset_view('test_assetsbundle.irasset_include1') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'directive': 'include', 'bundle': 'test_assetsbundle.irasset_include1', 'path': 'test_assetsbundle.manifest6', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irasset_include1')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irasset_include1') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1114,9 +1076,8 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_12_include2(self): - view = self.make_asset_view('test_assetsbundle.manifest6') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest6')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest6') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1127,7 +1088,6 @@ class TestAssetsManifest(AddonManifestPatched): ) def test_13_include_circular(self): - view = self.make_asset_view('test_assetsbundle.irasset_include1') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'directive': 'include', @@ -1141,17 +1101,15 @@ class TestAssetsManifest(AddonManifestPatched): 'path': 'test_assetsbundle.irasset_include1', }) - with self.assertRaises(QWebException) as cm: - self.env['ir.qweb']._render(view.id) - error = str(cm.exception.__cause__) + with self.assertRaises(Exception) as cm: + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irasset_include1') + bundle.js() + error = str(cm.exception) self.assertTrue(error) self.assertFalse(isinstance(error, RecursionError)) - self.assertTrue( - 'Circular assets bundle declaration:' in error - ) + self.assertIn('Circular assets bundle declaration:', error) def test_13_2_include_recursive_sibling(self): - view = self.make_asset_view('test_assetsbundle.irasset_include1') self.env['ir.asset'].create({ 'name': 'test_jsfile4', 'directive': 'include', @@ -1181,8 +1139,8 @@ class TestAssetsManifest(AddonManifestPatched): 'bundle': 'test_assetsbundle.irasset_include3', 'path': 'test_assetsbundle/static/src/js/test_jsfile1.js', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irasset_include1')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irasset_include1') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1204,9 +1162,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_other.mockmanifest1') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_other.mockmanifest1')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_other.mockmanifest1') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1228,9 +1185,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.manifest4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1255,9 +1211,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.manifest4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1282,9 +1237,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.manifest4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1307,9 +1261,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.manifest4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1331,13 +1284,11 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.manifest4') - rendered = self.env['ir.qweb']._render(view.id) - html_tree = lxml.etree.fromstring(rendered) - scripts = html_tree.findall('script') + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + scripts = [link for link in bundle.get_links() if link.endswith('js')] self.assertEqual(len(scripts), 2) - self.assertEqual(scripts[0].get('src'), 'http://external.link/external.js') - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + self.assertEqual(scripts[0], 'http://external.link/external.js') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1380,9 +1331,6 @@ class TestAssetsManifest(AddonManifestPatched): self.assertEqual(stylesheets[0].get('href'), 'http://external.css/externalstyle.css') self.assertEqual(stylesheets[0].get('media'), 'print') - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irasset2')], order='create_date DESC', limit=1) - self.assertEqual(len(attach), 1) - def test_20_css_base(self): self.env['ir.asset'].create({ 'name': '1', @@ -1394,20 +1342,10 @@ class TestAssetsManifest(AddonManifestPatched): 'bundle': 'test_assetsbundle.irasset2', 'path': 'test_assetsbundle/static/src/scss/test_file1.scss', }) - view = self.make_asset_view('test_assetsbundle.irasset2', { - 't-js': 'false', - 't-css': 'true', - }) - - rendered = self.env['ir.qweb']._render(view.id) - html_tree = lxml.etree.fromstring(rendered) - stylesheets = html_tree.findall('link') + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irasset2') + stylesheets = [link for link in bundle.get_links() if link.endswith('css')] self.assertEqual(len(stylesheets), 2) - self.assertEqual(stylesheets[0].get('href'), 'http://external.css/externalstyle.css') - for css in stylesheets: - self.assertFalse(css.get('media')) - - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irasset2')], order='create_date DESC', limit=1) + attach = bundle.css() content = attach.raw.decode() self.assertStringEqual( content, @@ -1430,9 +1368,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.bundle4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.bundle4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1460,9 +1397,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.bundle4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.bundle4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1494,9 +1430,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.bundle4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.bundle4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1524,9 +1459,8 @@ class TestAssetsManifest(AddonManifestPatched): ] } } - view = self.make_asset_view('test_assetsbundle.bundle4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.bundle4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1553,9 +1487,8 @@ class TestAssetsManifest(AddonManifestPatched): 'target': '/test_assetsbundle/static/src/js/test_jsfile3.js', 'directive': 'before', }) - view = self.make_asset_view('test_assetsbundle.bundle4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.bundle4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1582,9 +1515,8 @@ class TestAssetsManifest(AddonManifestPatched): 'target': '/test_assetsbundle/static/src/js/test_jsfile2.js', 'directive': 'after', }) - view = self.make_asset_view('test_assetsbundle.bundle4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.bundle4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1622,15 +1554,9 @@ class TestAssetsManifest(AddonManifestPatched): 't-js': 'true', 't-css': 'true', }) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.bundle4')], order='create_date DESC', limit=2) - attach_css = None - attach_js = None - for a in attach: - if '.css' in a.url: - attach_css = a - elif '.js' in a.url: - attach_js = a + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.bundle4') + attach_css = bundle.css() + attach_js = bundle.js() js_content = attach_js.raw.decode() self.assertStringEqual( @@ -1678,9 +1604,9 @@ class TestAssetsManifest(AddonManifestPatched): 'target': '/test_assetsbundle/static/src/js/doesnt_exist.js', 'directive': 'after', }) - view = self.make_asset_view('test_assetsbundle.wrong_path') with self.assertRaises(Exception) as cm: - self.env['ir.qweb']._render(view.id) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.wrong_path') + bundle.js() self.assertTrue( "test_assetsbundle/static/src/js/doesnt_exist.js not found" in str(cm.exception) ) @@ -1693,9 +1619,8 @@ class TestAssetsManifest(AddonManifestPatched): 'target': '/test_assetsbundle/static/src/js/test_jsfile3.js', 'directive': 'after', }) - view = self.make_asset_view('test_assetsbundle.manifest4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1722,9 +1647,8 @@ class TestAssetsManifest(AddonManifestPatched): 'target': '/test_assetsbundle/static/src/js/test_jsfile3.js', 'directive': 'before', }) - view = self.make_asset_view('test_assetsbundle.manifest4') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.manifest4')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.manifest4') + attach = bundle.js() content = attach.raw.decode() self.assertStringEqual( content, @@ -1755,9 +1679,8 @@ class TestAssetsManifest(AddonManifestPatched): 'bundle': 'test_assetsbundle.irassetsec', 'path': '/test_assetsbundle/%s' % path_to_dummy, }) - view = self.make_asset_view('test_assetsbundle.irassetsec') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irassetsec')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irassetsec') + attach = bundle.js() self.assertIn(b"Could not get content for /test_assetsbundle/../../tests/dummy.js", attach.exists().raw) @mute_logger('odoo.addons.base.models.ir_asset') @@ -1804,10 +1727,9 @@ class TestAssetsManifest(AddonManifestPatched): 'bundle': 'test_assetsbundle.irassetsec', 'path': '/notinstalled_module/somejsfile.js', }) - view = self.make_asset_view('test_assetsbundle.irassetsec') - with self.assertRaises(QWebException) as cm: - self.env['ir.qweb']._render(view.id) - + with self.assertRaises(Exception) as cm: + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irassetsec') + bundle.js() self.assertTrue('Unallowed to fetch files from addon notinstalled_module' in str(cm.exception)) def test_33bis_notinstalled_not_in_manifests(self): @@ -1827,10 +1749,9 @@ class TestAssetsManifest(AddonManifestPatched): 'bundle': 'test_assetsbundle.irassetsec', 'path': '/test_assetsbundle/__manifest__.py', }) - view = self.make_asset_view('test_assetsbundle.irassetsec') - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irassetsec')], order='create_date DESC', limit=1) - self.assertFalse(attach.exists()) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irassetsec') + links = bundle.get_links() + self.assertFalse(links) @mute_logger('odoo.addons.base.models.ir_asset') def test_35(self): @@ -1881,9 +1802,8 @@ class TestAssetsManifest(AddonManifestPatched): 'bundle': 'test_assetsbundle.irasset_custom_attach', 'path': 'test_assetsbundle/my_style_attach.scss', }) - view = self.make_asset_view('test_assetsbundle.irasset_custom_attach', {'t-css': True}) - self.env['ir.qweb']._render(view.id) - attach = self.env['ir.attachment'].search([('name', 'ilike', 'test_assetsbundle.irasset_custom_attach')], order='create_date DESC', limit=1) + bundle = self.env['ir.qweb']._get_asset_bundle('test_assetsbundle.irasset_custom_attach') + attach = bundle.css() content = attach.raw.decode() # The scss should be compiled self.assertStringEqual(