diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 7c3755d2cc3..1bb3663d7fc 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -1423,34 +1423,6 @@ class Website(models.Model): html_fields.append((model, table, name, translate)) return html_fields - def _get_snippets_assets(self): - """Returns every parent snippet asset from the database, filtering out - their potential overrides defined in other modules. As they share the same - snippet_id, asset_version and asset_type, it is possible to do that using - Postgres' DISTINCT ON and ordering by asset_id, as overriden assets will be - created later than their parents. - The assets are returned in the form of a list of tuples : - [(snippet_module, snippet_id, asset_version, asset_type, asset_id)] - """ - self.env.cr.execute(r""" - SELECT DISTINCT ON (snippet_id, asset_version, asset_type) - regexp_matches[1] AS snippet_module, - regexp_matches[2] AS snippet_id, - regexp_matches[3] AS asset_version, - CASE - WHEN regexp_matches[4]='scss' THEN 'css' - ELSE regexp_matches[4] - END AS asset_type, - id AS asset_id - FROM ( - SELECT REGEXP_MATCHES(PATH, '(\w*)\/.*\/snippets\/(\w*)\/(\d{3})\.(js|scss)'), - id - FROM ir_asset - ) AS regexp - ORDER BY snippet_id, asset_version, asset_type, asset_id; - """) - return self.env.cr.fetchall() - def _is_snippet_used(self, snippet_module, snippet_id, asset_version, asset_type, html_fields_attributes): snippet_occurences = [] # Check snippet template definition to avoid disabling its related assets. @@ -1488,22 +1460,28 @@ class Website(models.Model): return False def _disable_unused_snippets_assets(self): - snippets_assets = self._get_snippets_assets() + snippet_assets = self.env['ir.asset'].with_context(active_test=False).search_fetch( + [('path', 'like', '/static%/snippets/')], + ['active', 'path'], order='id') + snippet_re = re.compile(r'(\w*)\/.*\/snippets\/(\w*)\/(\d{3})(?:_\w*)?\.(js|scss)') + # regex will match /module/static/[.../]/snippets/snippet_id/XXX[_variable].asset_type + # _variable is not kept since only module, snippet_id, asset_version (XXX), asset_type are relevant html_fields_attributes = self._get_html_fields_attributes() - - for snippet_module, snippet_id, asset_version, asset_type, _asset_id in snippets_assets: - is_snippet_used = self._is_snippet_used(snippet_module, snippet_id, asset_version, asset_type, html_fields_attributes) - - # The regex catches XXX.scss, XXX.js and XXX_variables.scss - assets_regex = f'{snippet_id}/{asset_version}.+{asset_type}' - - # The query will also set to active or inactive assets overrides, as they - # share the same snippet_id, asset_version and filename_type as their parents - self.env.cr.execute(""" - UPDATE ir_asset - SET active = %(active)s - WHERE path ~ %(assets_regex)s - """, {"active": is_snippet_used, "assets_regex": assets_regex}) + snippet_used = {} + for snippet_asset in snippet_assets: + match = snippet_re.match(snippet_asset.path) + if not match: + continue + (snippet_module, snippet_id, asset_version, asset_type) = match.groups() + if asset_type == 'scss': + asset_type = 'css' + key = (snippet_id, asset_version, asset_type) # module is not relevant, we want the first one in the asset id order to filter module extension + if key not in snippet_used: + snippet_used[key] = self._is_snippet_used(snippet_module, snippet_id, asset_version, asset_type, html_fields_attributes) + is_snippet_used = snippet_used[key] + if is_snippet_used != snippet_asset.active: + snippet_asset.active = is_snippet_used + self.env['ir.asset'].flush_model() def _search_build_domain(self, domain, search, fields, extra=None): """ diff --git a/addons/website/tests/test_disable_unused_snippets_assets.py b/addons/website/tests/test_disable_unused_snippets_assets.py index 87a9d0f2f1f..148ebe7d10e 100644 --- a/addons/website/tests/test_disable_unused_snippets_assets.py +++ b/addons/website/tests/test_disable_unused_snippets_assets.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests import TransactionCase, tagged - +from unittest.mock import patch @tagged('post_install', '-at_install') class TestDisableSnippetsAssets(TransactionCase): @@ -68,7 +68,20 @@ class TestDisableSnippetsAssets(TransactionCase): 'mega_menu_content': MEGA_MENU_OUTDATED, }) self.mega_menu.flush_recordset() - self.Website._disable_unused_snippets_assets() + cache_clears = [] + + init_clear_cache = self.env.registry.clear_cache + def patched_clear_cache(*cache_names): + for cache_name in cache_names: + cache_clears.append(cache_name) + init_clear_cache(*cache_names) + + with patch.object(self.env.registry, 'clear_cache', patched_clear_cache): + self.Website._disable_unused_snippets_assets() + self.assertIn('assets', cache_clears, 'Assets cache should have been invalidated when updating ir_assets') + cache_clears.clear() + self.Website._disable_unused_snippets_assets() + self.assertNotIn('assets', cache_clears, 'No update on ir_assets expected, no invalidation should be triggered') s_website_form_000_scss = self._get_snippet_asset('s_website_form', '000', 'scss') s_website_form_001_scss = self._get_snippet_asset('s_website_form', '001', 'scss') diff --git a/odoo/addons/base/models/ir_asset.py b/odoo/addons/base/models/ir_asset.py index d9b8d9e9a02..bc363e859b7 100644 --- a/odoo/addons/base/models/ir_asset.py +++ b/odoo/addons/base/models/ir_asset.py @@ -68,7 +68,8 @@ class IrAsset(models.Model): return super().create(vals_list) def write(self, values): - self.env.registry.clear_cache('assets') + if self: + self.env.registry.clear_cache('assets') return super().write(values) def unlink(self):