[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
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/<path:extra>/<string:filename>',
|
||||
@http.route([
|
||||
'/web/assets/<path:extra>/<string:filename>',
|
||||
'/web/assets/<string:unique>/<path:extra>/<string:filename>'], 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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 <root> 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 ##*/'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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()), ("""<!DOCTYPE html>
|
||||
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"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="%(css)s"/>
|
||||
<link type="text/css" rel="stylesheet" href="{links[4]}"/>
|
||||
<meta/>
|
||||
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||
<script type="text/javascript" src="%(js)s" onerror="__odooAssetError=1"></script>
|
||||
<script type="text/javascript" src="{links[5]}" onerror="__odooAssetError=1"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>""" % format_data))
|
||||
</html>"""))
|
||||
|
||||
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()), ("""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="%(css)s"/>
|
||||
<link type="text/css" rel="stylesheet" href="/web/assets/debug/ltr/test_assetsbundle.bundle4.css"/>
|
||||
<meta/>
|
||||
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||
<script type="text/javascript" src="%(js)s" onerror="__odooAssetError=1"></script>
|
||||
<script type="text/javascript" src="/web/assets/debug/-/test_assetsbundle.bundle4.js" onerror="__odooAssetError=1"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>""" % format_data))
|
||||
</html>"""))
|
||||
|
||||
|
||||
@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(
|
||||
|
||||
Reference in New Issue
Block a user