[FIX] ir_qweb: assets: write in ir.attachment outside of main transaction
If, in the same transaction, we need to render a template and read its associated assets, they are unreachable because the writes in ir.attachment are not yet commited. Workaround this issue by commiting manually with new cursors. This issue occurs when you want to print a pdf report for the first time.
This commit is contained in:
@@ -22,6 +22,7 @@ import werkzeug
|
|||||||
from lxml import etree, html
|
from lxml import etree, html
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
import openerp.http
|
import openerp.http
|
||||||
import openerp.tools
|
import openerp.tools
|
||||||
@@ -40,6 +41,7 @@ from openerp.modules.module import get_resource_path
|
|||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_CSS_RULES = 4095
|
MAX_CSS_RULES = 4095
|
||||||
|
MOCK_ATTACH = namedtuple('mock_attach', ['url', 'last_update'])
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# QWeb template engine
|
# QWeb template engine
|
||||||
@@ -1214,11 +1216,26 @@ class AssetsBundle(object):
|
|||||||
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
|
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
|
||||||
return ira.unlink(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context)
|
return ira.unlink(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context)
|
||||||
|
|
||||||
|
def _mock_attachments(self, attachments):
|
||||||
|
""" As the assets are generated during the same transaction as the rendering of the
|
||||||
|
templates calling them, there is a scenario where the assets are unreachable: when
|
||||||
|
you make a request to read the assets while the transaction creating them is not done.
|
||||||
|
Indeed, when you make an asset request, the controller has to read the `ir.attachment`
|
||||||
|
table.
|
||||||
|
|
||||||
|
This scenario happens when you want to print a PDF report for the first
|
||||||
|
time, as the assets are not in cache and must be generated. To workaround this issue, we
|
||||||
|
need to open multiple cursors to write in the `ir.attachment` table outside of the ~main
|
||||||
|
transaction. But there's another tweak: a recordset browsed with a closed cursor becomes
|
||||||
|
unusable. We chose to copy their content in a namedtuple.
|
||||||
|
"""
|
||||||
|
return [MOCK_ATTACH(attachment.url, attachment['__last_update']) for attachment in attachments]
|
||||||
|
|
||||||
def get_attachments(self, type, inc=None):
|
def get_attachments(self, type, inc=None):
|
||||||
ira = self.registry['ir.attachment']
|
ira = self.registry['ir.attachment']
|
||||||
domain = [('url', '=like', '/web/content/%%-%s/%s%s.%s' % (self.version, self.xmlid, ('%%' if inc is None else '.%s' % inc), type))]
|
domain = [('url', '=like', '/web/content/%%-%s/%s%s.%s' % (self.version, self.xmlid, ('%%' if inc is None else '.%s' % inc), type))]
|
||||||
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
|
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
|
||||||
return ira.browse(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context)
|
return self._mock_attachments(ira.browse(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context))
|
||||||
|
|
||||||
def save_attachment(self, type, content, inc=None):
|
def save_attachment(self, type, content, inc=None):
|
||||||
ira = self.registry['ir.attachment']
|
ira = self.registry['ir.attachment']
|
||||||
@@ -1226,27 +1243,27 @@ class AssetsBundle(object):
|
|||||||
attachments = self.get_attachments(type, inc)
|
attachments = self.get_attachments(type, inc)
|
||||||
values = {}
|
values = {}
|
||||||
|
|
||||||
if not attachments:
|
with self.registry.cursor() as cr2:
|
||||||
values["name"] = "/web/content/%s" % type
|
if not attachments:
|
||||||
values["datas_fname"] = '%s%s.%s' % (self.xmlid, ('' if inc is None else '.%s' % inc), type)
|
values["name"] = "/web/content/%s" % type
|
||||||
values["res_model"] = 'ir.ui.view'
|
values["datas_fname"] = '%s%s.%s' % (self.xmlid, ('' if inc is None else '.%s' % inc), type)
|
||||||
values["public"] = True
|
values["res_model"] = 'ir.ui.view'
|
||||||
values["type"] = 'binary'
|
values["public"] = True
|
||||||
attachment_id = ira.create(self.cr, openerp.SUPERUSER_ID, values, context=self.context)
|
values["type"] = 'binary'
|
||||||
url = '/web/content/%s-%s/%s' % (attachment_id, self.version, values["datas_fname"])
|
attachment_id = ira.create(cr2, openerp.SUPERUSER_ID, values, context=self.context)
|
||||||
values["name"] = url
|
url = '/web/content/%s-%s/%s' % (attachment_id, self.version, values["datas_fname"])
|
||||||
values["url"] = url
|
values["name"] = url
|
||||||
else:
|
values["url"] = url
|
||||||
attachment_id = attachments[0].id
|
else:
|
||||||
|
attachment_id = attachments[0].id
|
||||||
|
|
||||||
values["datas"] = content.encode('utf8').encode('base64')
|
values["datas"] = content.encode('utf8').encode('base64')
|
||||||
ira.write(self.cr, openerp.SUPERUSER_ID, attachment_id, values, context=self.context)
|
ira.write(cr2, openerp.SUPERUSER_ID, attachment_id, values, context=self.context)
|
||||||
|
return self._mock_attachments(ira.browse(cr2, openerp.SUPERUSER_ID, attachment_id, context=self.context))[0]
|
||||||
return ira.browse(self.cr, openerp.SUPERUSER_ID, attachment_id, context=self.context)
|
|
||||||
|
|
||||||
def js(self):
|
def js(self):
|
||||||
attachments = self.get_attachments('js')
|
attachments = self.get_attachments('js')
|
||||||
if not attachments or attachments[0]['__last_update'] < Datetime.to_string(max([asset.last_modified for asset in self.javascripts])):
|
if not attachments or attachments[0].last_update < Datetime.to_string(max([asset.last_modified for asset in self.javascripts])):
|
||||||
content = ';\n'.join(asset.minify() for asset in self.javascripts)
|
content = ';\n'.join(asset.minify() for asset in self.javascripts)
|
||||||
return self.save_attachment('js', content)
|
return self.save_attachment('js', content)
|
||||||
return attachments[0]
|
return attachments[0]
|
||||||
@@ -1283,7 +1300,7 @@ class AssetsBundle(object):
|
|||||||
page = pages[-1]
|
page = pages[-1]
|
||||||
page_selectors = selectors
|
page_selectors = selectors
|
||||||
for idx, page in enumerate(pages):
|
for idx, page in enumerate(pages):
|
||||||
attachments |= self.save_attachment("css", ' '.join(page), inc=idx)
|
attachments.append(self.save_attachment("css", ' '.join(page), inc=idx))
|
||||||
return attachments
|
return attachments
|
||||||
|
|
||||||
def css_message(self, message):
|
def css_message(self, message):
|
||||||
@@ -1540,24 +1557,25 @@ class PreprocessedCSS(StylesheetAsset):
|
|||||||
def to_html(self):
|
def to_html(self):
|
||||||
if self.url:
|
if self.url:
|
||||||
try:
|
try:
|
||||||
ira = self.registry['ir.attachment']
|
with self.registry.cursor() as cr3:
|
||||||
url = self.html_url % self.url
|
ira = self.registry['ir.attachment']
|
||||||
domain = [('type', '=', 'binary'), ('url', '=', url)]
|
url = self.html_url % self.url
|
||||||
with self.cr.savepoint():
|
domain = [('type', '=', 'binary'), ('url', '=', url)]
|
||||||
ira_id = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
|
with cr3.savepoint():
|
||||||
datas = self.content.encode('utf8').encode('base64')
|
ira_id = ira.search(cr3, openerp.SUPERUSER_ID, domain, context=self.context)
|
||||||
if ira_id:
|
datas = self.content.encode('utf8').encode('base64')
|
||||||
# TODO: update only if needed
|
if ira_id:
|
||||||
ira.write(self.cr, openerp.SUPERUSER_ID, ira_id, {'datas': datas},
|
# TODO: update only if needed
|
||||||
context=self.context)
|
ira.write(cr3, openerp.SUPERUSER_ID, ira_id, {'datas': datas},
|
||||||
else:
|
context=self.context)
|
||||||
ira.create(self.cr, openerp.SUPERUSER_ID, dict(
|
else:
|
||||||
datas=datas,
|
ira.create(cr3, openerp.SUPERUSER_ID, dict(
|
||||||
mimetype='text/css',
|
datas=datas,
|
||||||
type='binary',
|
mimetype='text/css',
|
||||||
name=url,
|
type='binary',
|
||||||
url=url,
|
name=url,
|
||||||
), context=self.context)
|
url=url,
|
||||||
|
), context=self.context)
|
||||||
except psycopg2.Error:
|
except psycopg2.Error:
|
||||||
pass
|
pass
|
||||||
return super(PreprocessedCSS, self).to_html()
|
return super(PreprocessedCSS, self).to_html()
|
||||||
|
|||||||
Reference in New Issue
Block a user