Files
odoo_source/addons/website/tests/test_disable_unused_snippets_assets.py
T
Xavier-Do 76adca8ec9 [FIX] website: clear cache on ir.asset archive
Since #121376 a clear_cache was removed when unlinking an attachment

This clear_cache was not useful when restarting a server with new
sources, an other operation changing the content of an assets should
invalidate the cache manually. This is the case of ir.asset CUD
operations.

Unfortunately, a manual update was left missing in website, when
archiving ir.assets used for snippets.

This was discovered on runbot, with two workers, when the first workers
generates assets before the cron, and the second one after the cron.

The second worker unlinks attachments creating an inconsistency in the
cache of the first one.

This problem can be solved quickly by invalidating the assets cache in
the cron manually but this will be done in all cases. The proposed
solution will check the ir.asset that should change and only clear the
cache if the state changed.

Regarding performances, this should actually be a slight improvement in
query count since at the cost of one more select to prefetch the record
we can avoid multiple update, one per snippet. In most case no update at
all should be done, at most 2 can be done (one for archive, one for
unarchive).

Example with some assets to unarchive:

Before:
TOTAL ENTRIES: 277
SELECT: 158 (~0.16591858863830566s)
UNKWOW: 54 (~0.0202481746673584s)
UPDATE: 65 (~0.03202557563781738s)

After:
TOTAL ENTRIES: 214
SELECT: 159 (~0.1663439826965332s)
UNKWOW: 54 (~0.029229164123535156s)
UPDATE: 1 (~0.0002865791320800781s)

If the number of query is lowered, the python processing is slightly
higher. Locally the test time is similar, slightly longer since an
additional call to _disable_unused_snippets_assets was added to check
the cache invalidation

closes odoo/odoo#130973

X-original-commit: 3aeab51ff4379e6d76172614fd39f09d6c449ac6
Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
2023-08-06 17:58:53 +02:00

181 lines
8.5 KiB
Python

# -*- coding: utf-8 -*-
# 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):
def setUp(self):
super().setUp()
self.View = self.env['ir.ui.view']
self.WebsiteMenu = self.env['website.menu']
self.Website = self.env['website']
self.IrAsset = self.env['ir.asset']
self.homepage = self.View.create({
'name': 'Home',
'type': 'qweb',
'arch_db': HOMEPAGE_OUTDATED,
'key': 'website.homepage',
})
self.mega_menu = self.WebsiteMenu.create({
'name': 'Image Gallery V001',
'mega_menu_content': MEGA_MENU_UP_TO_DATE,
})
self.initial_active_snippets_assets = self._get_active_snippets_assets()
def test_homepage_outdated_and_mega_menu_up_to_date(self):
self.Website._disable_unused_snippets_assets()
# Old snippet with scss and js
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')
s_website_form_000_js = self._get_snippet_asset('s_website_form', '000', 'js')
self.assertEqual(s_website_form_000_scss.active, True)
self.assertEqual(s_website_form_001_scss.active, True)
self.assertEqual(s_website_form_000_js.active, True)
# Old snippet with scss and scss variables
s_masonry_block_000_scss = self._get_snippet_asset('s_masonry_block', '000', 'scss')
s_masonry_block_000_variables_scss = self._get_snippet_asset('s_masonry_block', '000_variables', 'scss')
s_masonry_block_001_scss = self._get_snippet_asset('s_masonry_block', '001', 'scss')
self.assertEqual(s_masonry_block_000_scss.active, True)
self.assertEqual(s_masonry_block_000_variables_scss.active, True)
self.assertEqual(s_masonry_block_001_scss.active, True)
# New snippet
s_image_gallery_000 = self._get_snippet_asset('s_image_gallery', '000', 'scss')
s_image_gallery_001 = self._get_snippet_asset('s_image_gallery', '001', 'scss')
self.assertEqual(s_image_gallery_000.active, False)
self.assertEqual(s_image_gallery_001.active, True)
unwanted_snippets_assets_changes = set(self.initial_active_snippets_assets) - set(self._get_active_snippets_assets()) - set([s_image_gallery_000.path])
# The vaccuum should not have activated/deactivated any other snippet asset than the original ones
self.assertEqual(
len(unwanted_snippets_assets_changes),
0,
'Following snippets are not following the snippet versioning system structure, or their previous assets have not been deactivated:\n'
+ '\n'.join(unwanted_snippets_assets_changes))
def test_homepage_up_to_date_and_mega_menu_outdated(self):
self.homepage.write({
'arch_db': HOMEPAGE_UP_TO_DATE,
})
self.homepage.flush_recordset()
self.mega_menu.write({
'mega_menu_content': MEGA_MENU_OUTDATED,
})
self.mega_menu.flush_recordset()
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')
s_website_form_000_js = self._get_snippet_asset('s_website_form', '000', 'js')
self.assertEqual(s_website_form_000_scss.active, False)
self.assertEqual(s_website_form_001_scss.active, True)
self.assertEqual(s_website_form_000_js.active, True)
s_masonry_block_000_scss = self._get_snippet_asset('s_masonry_block', '000', 'scss')
s_masonry_block_000_variables_scss = self._get_snippet_asset('s_masonry_block', '000_variables', 'scss')
s_masonry_block_001_scss = self._get_snippet_asset('s_masonry_block', '001', 'scss')
self.assertEqual(s_masonry_block_000_scss.active, False)
self.assertEqual(s_masonry_block_000_variables_scss.active, False)
self.assertEqual(s_masonry_block_001_scss.active, True)
s_image_gallery_000 = self._get_snippet_asset('s_image_gallery', '000', 'scss')
s_image_gallery_001 = self._get_snippet_asset('s_image_gallery', '001', 'scss')
self.assertEqual(s_image_gallery_000.active, True)
self.assertEqual(s_image_gallery_001.active, True)
def _get_snippet_asset(self, snippet_id, asset_version, asset_type):
return self.IrAsset.search([('path', '=', 'website/static/src/snippets/' + snippet_id + '/' + asset_version + '.' + asset_type)], limit=1)
def _get_active_snippets_assets(self):
return self.IrAsset.search([('path', 'like', 'snippets'), ('active', '=', True)]).mapped('path')
HOMEPAGE_UP_TO_DATE = """
<t name="Homepage" t-name="website.homepage1">
<t t-call="website.layout">
<t t-set="pageName" t-value="'homepage'"/>
<div id="wrap" class="oe_structure oe_empty">
<section class="s_website_form pt16 pb16 o_colored_level" data-vcss="001" data-snippet="s_website_form" data-name="Form">
<div class="container">
<form action="/website_form/" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*" data-success-mode="redirect" data-success-page="/contactus-thank-you" data-model_name="mail.mail">
</form>
</div>
</section>
<section class="s_masonry_block" data-vcss="001" data-snippet="s_masonry_block" data-name="Masonry">
<div class="container-fluid"/>
</section>
<section class="s_showcase pt48 pb48 o_colored_level" data-vcss="002" data-snippet="s_showcase" data-name="Showcase">
<div class="container">
</div>
</section>
</div>
</t>
</t>
"""
HOMEPAGE_OUTDATED = """
<t name="Homepage" t-name="website.homepage1">
<t t-call="website.layout">
<t t-set="pageName" t-value="'homepage'"/>
<div id="wrap" class="oe_structure oe_empty">
<form action="/website_form/" method="post" class="s_website_form container-fluid mt32 o_fake_not_editable" enctype="multipart/form-data" data-name="Form Builder" data-model_name="mail.mail" data-success_page="/contactus-thank-you" data-snippet="s_website_form">
<div class="container">
</div>
</form>
<section class="s_masonry_block" data-vcss="001" data-snippet="s_masonry_block" data-name="Masonry">
<div class="container-fluid"/>
</section>
<section class="s_masonry_block" data-snippet="s_masonry_block" data-name="Masonry">
<div class="container-fluid"/>
</section>
<section class="s_showcase pt48 pb48 o_colored_level" data-vcss="002" data-snippet="s_showcase" data-name="Showcase">
<div class="container">
</div>
</section>
</div>
</t>
</t>
"""
MEGA_MENU_UP_TO_DATE = """
<section class="s_mega_menu_multi_menus py-4 o_colored_level" data-name="Multi-Menus">
<div class="container">
</div>
</section>
<section class="s_image_gallery o_slideshow s_image_gallery_show_indicators s_image_gallery_indicators_rounded pt24 o_colored_level" data-vcss="001" data-columns="3" style="height: 500px; overflow: hidden;" data-snippet="s_image_gallery" data-name="Image Gallery">
<div class="container">
</div>
</section>
"""
MEGA_MENU_OUTDATED = """
<section class="s_mega_menu_multi_menus py-4 o_colored_level" data-name="Multi-Menus">
<div class="container">
</div>
</section>
<section class="s_image_gallery o_slideshow s_image_gallery_show_indicators s_image_gallery_indicators_rounded pt24 o_colored_level" data-columns="3" style="height: 500px; overflow: hidden;" data-snippet="s_image_gallery" data-name="Image Gallery">
<div class="container">
</div>
</section>
"""