[FIX] base: ir_qweb_field:image: handle webp mimetype
See discussions on https://github.com/odoo/odoo/pull/85494/. TLDR: webp image format needs to be supported, but we should avoid going through the Pillow library as it is largely unsafe for that format. jpg attachment are created in JS at upload time. Wkhtmltopdf doesn't support webp, so, in reports, we should display one of those jpg copies. This work is handled by `ir.qweb: _get_converted_image_data_uri` which is used as: ```xml <img src="image_data_uri(some_b64value)" /> ``` The mentioned PR did not however adapt the ir.qweb.field.image that, when passed the option `qweb_img_raw_data` should return a base64 url such as `data:[mimetype],base64,[datas]`. usage: ```xml <span t-field="object.image_field" t-options-widget="'image'" t-options-qweb_img_raw_data="1" /> ``` Hence, before this commit, there was a crash as we tried to pass that value to PIL. After this commit, there is no crash, and the image displays correctly as JPG in the PDF opw-3859423 closes odoo/odoo#163003 X-original-commit: 9056a4b1f28e820c0444f28367cc30abebd5ea30 Signed-off-by: Benoit Socias (bso) <bso@odoo.com> Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import base64
|
||||
from lxml import etree
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
|
||||
class TestIrQweb(TransactionCase):
|
||||
def test_image_field(self):
|
||||
@@ -28,3 +31,44 @@ class TestIrQweb(TransactionCase):
|
||||
self.assertTrue(img.get("src").startswith("/web/image"))
|
||||
self.assertEqual(img.get("class"), "img img-fluid")
|
||||
self.assertEqual(img.get("alt"), "test image partner")
|
||||
|
||||
def test_image_field_webp(self):
|
||||
webp = "UklGRsCpAQBXRUJQVlA4WAoAAAAQAAAAGAQA/wMAQUxQSMywAAAdNANp22T779/0RUREkvqLOTPesG1T21jatpLTSbpXQzTMEw3zWMM81jCPnWG2fTM7vpndvpkd38y2758Y+6a/Ld/Mt3zzT/XwzCKlV0Ooo61UpZIsKLjKc98R"
|
||||
webp_decoded = base64.b64decode(webp)
|
||||
self.assertEqual(guess_mimetype(webp_decoded), "image/webp")
|
||||
|
||||
view = self.env["ir.ui.view"].create({
|
||||
"key": "web.test_qweb",
|
||||
"type": "qweb",
|
||||
"arch": """<t t-name="test_qweb">
|
||||
<span t-field="record.flag_image" t-options-widget="'image'" t-options-qweb_img_raw_data="is_raw_image" />
|
||||
</t>"""
|
||||
})
|
||||
lang_record = self.env["res.lang"].create({
|
||||
"name": "test lang",
|
||||
"flag_image": webp,
|
||||
"code": "TEST"
|
||||
})
|
||||
attachment = self.env["ir.attachment"].search([
|
||||
("res_model", "=", "res.lang"),
|
||||
("res_id", '=', lang_record.id),
|
||||
("res_field", "=", "flag_image")
|
||||
])
|
||||
|
||||
jpeg_attach = self.env["ir.attachment"].create({
|
||||
"name": "webpcopy.jpg",
|
||||
"res_model": "ir.attachment",
|
||||
"res_id": attachment.id,
|
||||
"datas": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAF0lEQVR4nGJxKFrEwMDAxAAGgAAAAP//D+IBWx9K7TUAAAAASUVORK5CYII="
|
||||
})
|
||||
jpeg_datas = jpeg_attach.datas
|
||||
|
||||
html = view.with_context(webp_as_jpg=False)._render_template(view.id, {"is_raw_image": True, "record": lang_record})
|
||||
tree = etree.fromstring(html)
|
||||
img = tree.find("img")
|
||||
self.assertEqual(img.get("src"), "data:image/webp;base64,%s" % webp)
|
||||
|
||||
html = view.with_context(webp_as_jpg=True)._render_template(view.id, {"is_raw_image": True, "record": lang_record})
|
||||
tree = etree.fromstring(html)
|
||||
img = tree.find("img")
|
||||
self.assertEqual(img.get("src"), "data:image/png;base64,%s" % jpeg_datas.decode())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import binascii
|
||||
from datetime import time
|
||||
import logging
|
||||
import re
|
||||
@@ -15,6 +16,7 @@ from odoo import api, fields, models, _, _lt, tools
|
||||
from odoo.tools import posix_to_ldml, float_utils, format_date, format_duration, pycompat
|
||||
from odoo.tools.mail import safe_attrs
|
||||
from odoo.tools.misc import get_lang, babel_locale_parse
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -390,13 +392,21 @@ class ImageConverter(models.AbstractModel):
|
||||
|
||||
@api.model
|
||||
def _get_src_data_b64(self, value, options):
|
||||
try: # FIXME: maaaaaybe it could also take raw bytes?
|
||||
image = Image.open(BytesIO(base64.b64decode(value)))
|
||||
try:
|
||||
img_b64 = base64.b64decode(value)
|
||||
except binascii.Error:
|
||||
raise ValueError("Invalid image content") from None
|
||||
|
||||
if img_b64 and guess_mimetype(img_b64, '') == 'image/webp':
|
||||
return self.env["ir.qweb"]._get_converted_image_data_uri(value)
|
||||
|
||||
try:
|
||||
image = Image.open(BytesIO(img_b64))
|
||||
image.verify()
|
||||
except IOError:
|
||||
raise ValueError("Non-image binary fields can not be converted to HTML")
|
||||
raise ValueError("Non-image binary fields can not be converted to HTML") from None
|
||||
except: # image.verify() throws "suitable exceptions", I have no idea what they are
|
||||
raise ValueError("Invalid image content")
|
||||
raise ValueError("Invalid image content") from None
|
||||
|
||||
return "data:%s;base64,%s" % (Image.MIME[image.format], value.decode('ascii'))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user