From 0b22f4deee3129a84daa7113393da6e7200de6ff Mon Sep 17 00:00:00 2001 From: Paul Morelle Date: Mon, 24 Jan 2022 15:05:36 +0000 Subject: [PATCH] [FIX] base: keep cache when regenerating assets When the assets are regenerated, the previous attachments are deleted. This may happen during the execution of t-call-assets directives in a qweb view. However, some properties that have been accessed with a sudo() were accessible in the cache. Clearing the cache during the view rendering could lead to access errors, which wouldn't be present without this directive. This commit works around this problem by removing these attachments with a SQL query, without relying on the classic unlink, so that the cache is preserved in this case. By the way, sanitize the filename when marking it for deletion. closes odoo/odoo#83570 X-original-commit: 627d508edfb62449067a05b0e3c1a0004f0af32c Signed-off-by: Raphael Collet Signed-off-by: Olivier Dony Signed-off-by: Paul Morelle --- odoo/addons/base/models/assetsbundle.py | 16 ++++++++++++++-- odoo/addons/base/models/ir_attachment.py | 1 + .../test_assetsbundle/tests/test_assetsbundle.py | 8 ++++---- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/odoo/addons/base/models/assetsbundle.py b/odoo/addons/base/models/assetsbundle.py index f28aa3539bf..63dc107c635 100644 --- a/odoo/addons/base/models/assetsbundle.py +++ b/odoo/addons/base/models/assetsbundle.py @@ -222,6 +222,18 @@ class AssetsBundle(object): def get_debug_asset_url(self, extra='', name='%', extension='%'): return f"/web/assets/debug/{extra}{name}{extension}" + def _unlink_attachments(self, attachments): + """ Unlinks attachments without actually calling unlink, so that the ORM cache is not cleared. + + Specifically, if an attachment is generated while a view is rendered, clearing the ORM cache + could unload fields loaded with a sudo(), and expected to be readable by the view. + Such a view would be website.layout when main_object is an ir.ui.view. + """ + to_delete = set(attach.store_fname for attach in attachments if attach.store_fname) + self.env.cr.execute(f"DELETE FROM {attachments._table} WHERE id IN %s", [tuple(attachments.ids)]) + for file_path in to_delete: + attachments._file_delete(file_path) + def clean_attachments(self, extension): """ Takes care of deleting any outdated ir.attachment records associated to a bundle before saving a fresh one. @@ -248,7 +260,7 @@ class AssetsBundle(object): # avoid to invalidate cache if it's already empty (mainly useful for test) if attachments: - attachments.unlink() + self._unlink_attachments(attachments) # force bundle invalidation on other workers self.env['ir.qweb'].clear_caches() @@ -591,7 +603,7 @@ class AssetsBundle(object): compiled = self.run_rtlcss(compiled) if not self.css_errors and old_attachments: - old_attachments.unlink() + self._unlink_attachments(old_attachments) old_attachments = None fragments = self.rx_css_split.split(compiled) diff --git a/odoo/addons/base/models/ir_attachment.py b/odoo/addons/base/models/ir_attachment.py index 815d0781ee4..50003009992 100644 --- a/odoo/addons/base/models/ir_attachment.py +++ b/odoo/addons/base/models/ir_attachment.py @@ -142,6 +142,7 @@ class IrAttachment(models.Model): def _mark_for_gc(self, fname): """ Add ``fname`` in a checklist for the filestore garbage collection. """ + fname = re.sub('[.]', '', fname).strip('/\\') # we use a spooldir: add an empty file in the subdirectory 'checklist' full_path = os.path.join(self._full_path('checklist'), fname) if not os.path.exists(full_path): diff --git a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py index 6ee0c60acad..35f43462db5 100644 --- a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py +++ b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py @@ -736,19 +736,19 @@ class TestAssetsBundleWithIRAMock(FileTouchable): # patch methods 'create' and 'unlink' of model 'ir.attachment' origin_create = IrAttachment.create - origin_unlink = IrAttachment.unlink + origin_unlink = AssetsBundle._unlink_attachments @api.model_create_multi def create(self, vals_list): counter.update(['create'] * len(vals_list)) return origin_create(self, vals_list) - def unlink(self): + def unlink(self, attachments): counter.update(['unlink']) - return origin_unlink(self) + return origin_unlink(self, attachments) self.patch(IrAttachment, 'create', create) - self.patch(IrAttachment, 'unlink', unlink) + self.patch(AssetsBundle, '_unlink_attachments', unlink) def _get_asset(self): files, _ = self.env['ir.qweb']._get_asset_content(self.stylebundle_name)