[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 <rco@odoo.com>
Signed-off-by: Olivier Dony <odo@odoo.com>
Signed-off-by: Paul Morelle <pmo@odoo.com>
This commit is contained in:
Paul Morelle
2022-01-28 14:10:15 +00:00
parent 48700416c1
commit 0b22f4deee
3 changed files with 19 additions and 6 deletions
+14 -2
View File
@@ -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)
+1
View File
@@ -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):
@@ -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)