[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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user