The main motivation is to be able to generate assets bundle outside the t-call-assets call. The need of an id in the url makes it mandatory to have an attachment when adding the url in the page. Without this restriction, we can guess the url without generating the assets. This can also have other useful side effect: There are corner case when a worked could have an invalid url in cache because, if the transaction is rollbacked or if another request generates the same attachment at the same time. This should be partially solved by removing the id: The url remains valid even if the attachment does not exist. Note that the extra part of the url was made explicit, always there and taking one / to remove complexity and ambiguity. Note that an additional query appeared in .test_50_perf_sql_web_assets because of the search, this but two of them were in _find_record. One of them was an `exist`, not making much sense since we are not getting the id from the attachment url anymore but from a search, and the other one was prefetch of the "public field" since the call to _find_record does not go in other cases (xmlid, website published, access token, ....). A attachment of a asset is always public, and this part of the security was moved to the search domain. The final result is one less query: - one query to search - one query to read the fields (_get_stream_from) (the prefetch could actually be set to avoid prefetching everything) Part-of: odoo/odoo#131353
113 lines
4.6 KiB
Python
113 lines
4.6 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
import time
|
|
|
|
import odoo
|
|
import odoo.tests
|
|
|
|
from odoo.tests.common import HttpCase
|
|
from odoo.modules.module import get_manifest
|
|
from odoo.tools import mute_logger
|
|
|
|
from unittest.mock import patch
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TestAssetsGenerateTimeCommon(odoo.tests.TransactionCase):
|
|
|
|
def generate_bundles(self, unlink=True):
|
|
if unlink:
|
|
self.env['ir.attachment'].search([('url', '=like', '/web/assets/%')]).unlink() # delete existing attachement
|
|
installed_module_names = self.env['ir.module.module'].search([('state', '=', 'installed')]).mapped('name')
|
|
bundles = {
|
|
key
|
|
for module in installed_module_names
|
|
for key in get_manifest(module).get('assets', [])
|
|
}
|
|
|
|
for bundle 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)
|
|
except ValueError:
|
|
_logger.info('Error detected while generating bundle %r %s', bundle, assets_type)
|
|
|
|
|
|
@odoo.tests.tagged('post_install', '-at_install', 'assets_bundle')
|
|
class TestLogsAssetsGenerateTime(TestAssetsGenerateTimeCommon):
|
|
|
|
def test_logs_assets_generate_time(self):
|
|
"""
|
|
The purpose of this test is to monitor the time of assets bundle generation.
|
|
This is not meant to test the generation failure, hence the try/except and the mute logger.
|
|
"""
|
|
for bundle, duration in list(self.generate_bundles()):
|
|
_logger.info('Bundle %r generated in %.2fs', bundle, duration)
|
|
|
|
def test_logs_assets_check_time(self):
|
|
"""
|
|
The purpose of this test is to monitor the time of assets bundle generation.
|
|
This is not meant to test the generation failure, hence the try/except and the mute logger.
|
|
"""
|
|
start = time.time()
|
|
for bundle, duration in self.generate_bundles(False):
|
|
_logger.info('Bundle %r checked in %.2fs', bundle, duration)
|
|
duration = time.time() - start
|
|
_logger.info('All bundle checked in %.2fs', duration)
|
|
|
|
|
|
@odoo.tests.tagged('post_install', '-at_install', '-standard', 'test_assets')
|
|
class TestPregenerateTime(HttpCase):
|
|
|
|
def test_logs_pregenerate_time(self):
|
|
self.env['ir.qweb']._pregenerate_assets_bundles()
|
|
start = time.time()
|
|
self.env.registry.clear_cache()
|
|
self.env.cache.invalidate()
|
|
with self.profile(collectors=['sql', odoo.tools.profiler.PeriodicCollector(interval=0.01)], disable_gc=True):
|
|
self.env['ir.qweb']._pregenerate_assets_bundles()
|
|
duration = time.time() - start
|
|
_logger.info('All bundle checked in %.2fs', duration)
|
|
|
|
@odoo.tests.tagged('post_install', '-at_install', '-standard', 'assets_bundle')
|
|
class TestAssetsGenerateTime(TestAssetsGenerateTimeCommon):
|
|
"""
|
|
This test is meant to be run nightly to ensure bundle generation does not exceed
|
|
a low threshold
|
|
"""
|
|
|
|
def test_assets_generate_time(self):
|
|
thresholds = {
|
|
'web.qunit_suite_tests.js': 3.6,
|
|
'project.webclient.js': 2.5,
|
|
'point_of_sale.pos_assets_backend.js': 2.5,
|
|
'web.assets_backend.js': 2.5,
|
|
}
|
|
for bundle, duration in self.generate_bundles():
|
|
threshold = thresholds.get(bundle, 2)
|
|
self.assertLess(duration, threshold, "Bundle %r took more than %s sec" % (bundle, threshold))
|
|
|
|
@odoo.tests.tagged('post_install', '-at_install')
|
|
class TestLoad(HttpCase):
|
|
def test_assets_already_exists(self):
|
|
self.authenticate('admin', 'admin')
|
|
# TODO xdo adapt this test. url open won't generate attachment anymore even if not pregenerated
|
|
_save_attachment = odoo.addons.base.models.assetsbundle.AssetsBundle.save_attachment
|
|
|
|
def save_attachment(bundle, extension, content):
|
|
attachment = _save_attachment(bundle, extension, content)
|
|
message = f"Trying to save an attachement for {bundle.name} when it should already exist: {attachment.url}"
|
|
_logger.error(message)
|
|
return attachment
|
|
|
|
with patch('odoo.addons.base.models.assetsbundle.AssetsBundle.save_attachment', save_attachment):
|
|
self.url_open('/web').raise_for_status()
|
|
self.url_open('/').raise_for_status()
|