Skip to content
Snippets Groups Projects
Commit ac6f9091291b authored by Vincent Hatakeyama's avatar Vincent Hatakeyama
Browse files

:sparkles: Field converter on a binary field use data uri

parent 85bcb83808e8
No related tags found
1 merge request!64✨ Field converter on a binary field use data uri
This commit is part of merge request !64. Comments created here will be created in the context of that merge request.
Changelog
=========
18.0.6.1.0
----------
Field converter on a binary field use data uri.
18.0.6.0.0
----------
......
##############################################################################
#
# Converter Odoo module
# Copyright © 2020 XCG Consulting <https://xcg-consulting.fr>
# Copyright © 2020, 2025 XCG Consulting <https://xcg-consulting.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
......
......@@ -21,7 +21,7 @@
"name": "Converter",
"license": "AGPL-3",
"summary": "Convert odoo records to/from plain data structures.",
"version": "18.0.6.0.0",
"version": "18.0.6.1.0",
"category": "Hidden",
"author": "XCG Consulting",
"website": "https://orbeet.io/",
......
##############################################################################
#
# Converter Odoo module
# Copyright © 2020 XCG Consulting <https://xcg-consulting.fr>
# Copyright © 2020, 2025 XCG Consulting <https://xcg-consulting.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
......@@ -17,7 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
import datetime
from collections.abc import Callable
from typing import Any, Literal
......@@ -21,5 +21,7 @@
import datetime
from collections.abc import Callable
from typing import Any, Literal
from urllib.parse import urlsplit
from urllib.request import urlopen
import pytz
......@@ -24,4 +26,4 @@
import pytz
from odoo import api, models # type: ignore[import-untyped]
from odoo import api, fields, models # type: ignore[import-untyped]
......@@ -27,4 +29,11 @@
from .base import PHASE_POSTCREATE, Context, Converter, Newinstance, Skip, SkipType
from .base import (
PHASE_POSTCREATE,
Context,
Newinstance,
PostHookConverter,
Skip,
SkipType,
)
......@@ -29,6 +38,6 @@
class Field(Converter):
class Field(PostHookConverter):
"""Converter linked to a single field"""
def __init__(
......@@ -111,6 +120,20 @@
if isinstance(value, datetime.date):
if not self._message_formatter:
value = value.isoformat()
field = instance._fields[self.field_name]
if isinstance(field, fields.Binary):
if field.attachment:
domain = [
("res_model", "=", instance._name),
("res_field", "=", self.field_name),
("res_id", "in", instance.ids),
]
# sudo needed, no right to read those attachments
attachment = instance.env["ir.attachment"].sudo().search(domain)
mimetype = attachment.mimetype.encode("UTF-8")
else:
mimetype = b"application/octet-stream"
value = b"data:" + mimetype + b";base64," + value
if self._message_formatter:
value = self._message_formatter(value, False)
return value
......@@ -135,8 +158,20 @@
value = self.odoo_to_message(instance)
if isinstance(value, SkipType) or value == message_value:
return {}
field = instance._fields[self.field_name]
if isinstance(field, fields.Binary):
if field.attachment:
return {}
else:
url = urlsplit(message_value)
if url.scheme != "data":
# urlsplit raises ValueError too
raise ValueError("Not a Data URI (%s scheme)", url.scheme)
with urlopen(message_value) as response:
message_value = base64.b64encode(response.read())
if self._odoo_formatter:
message_value = self._odoo_formatter(message_value)
return {self.field_name: message_value}
......@@ -138,8 +173,36 @@
if self._odoo_formatter:
message_value = self._odoo_formatter(message_value)
return {self.field_name: message_value}
def post_hook(self, instance: models.BaseModel, message_data):
field = instance._fields[self.field_name]
if isinstance(field, fields.Binary):
if field.attachment:
# create ir.attachment directly
url = urlsplit(message_data)
# sudo needed, no right to read those attachments
if url.scheme != "data":
# urlsplit raises ValueError too
raise ValueError("Not a Data URI (%s scheme)", url.scheme)
domain = [
("res_model", "=", instance._name),
("res_field", "=", self.field_name),
("res_id", "in", instance.ids),
]
# sudo needed, no right to read those attachments
attachment = instance.env["ir.attachment"].sudo().search(domain)
with urlopen(message_data) as response:
datas = base64.b64encode(response.read())
if self._odoo_formatter:
datas = self._odoo_formatter(datas)
# TODO does not update the data. Need to use _set_attachment_data
# directly maybe?
attachment.write(
{"datas": datas, "mimetype": url.path.split(";", 1)[0]}
)
instance.invalidate_recordset([self.field_name])
class TranslatedSelection(Field):
"""Converter that uses a translation value of a selection field rather
......
##############################################################################
#
# Converter Odoo module
# Copyright © 2021 XCG Consulting <https://xcg-consulting.fr>
# Copyright © 2021, 2025 XCG Consulting <https://xcg-consulting.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
......@@ -110,3 +110,84 @@
)
),
)
def test_binary_data_uri(self):
converter = Field("avatar_1920")
partner = self.env.ref("base.partner_admin")
old_image = partner.avatar_1920
value = converter.odoo_to_message(partner)
# admin partner avatar is stored with application/octet-stream
self.assertTrue(value.startswith(b"data:application/octet-stream;base64,"))
# white 1920x1920 image
data_uri = """data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAB4AAAAeAAQAAAAAH2XdrAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAd2KE6QAAAAHdElN
RQfpAhURBCXSFMwjAAAIE0lEQVR42u3PAQ0AMAjAMPyb/mWQjE7BOu9Ysz0ADAwMDAwMfCfgesD1
gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOu
B1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5w
PeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA
6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64H
XA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA9
4HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDr
AdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdc
D7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3g
esD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB
1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wP
uB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6
wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHX
A64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4
HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA
9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcD
rgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7ge
cD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1
gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOu
B1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5w
PeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA
6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64H
XA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA9
4HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDr
AdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdc
D7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3g
esD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB
1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wP
uB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6
wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHX
A64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4
HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA
9YDrAdcDrgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcD
rgdcD7gecD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7ge
cD3gesD1gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1
gOsB1wOuB1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrAdcDrgdcD7gecD3gesD1gOsB1wOu
B1wPuB5wPeB6wPWA6wHXA64HXA+4HnA94HrA9YDrfdjxYRCgcj/RAAAAJXRFWHRkYXRlOmNyZWF0
ZQAyMDI1LTAyLTIxVDE3OjA0OjM3KzAwOjAw60rorAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNS0w
Mi0yMVQxNzowNDozNyswMDowMJoXUBAAAAAASUVORK5CYII="""
values = converter.message_to_odoo(self.env, "update", data_uri, partner, True)
partner.write(values)
self.assertNotEqual(old_image, partner.avatar_1920)
value = converter.odoo_to_message(partner)
# not an attachment, mimetype is lost
self.assertTrue(value.startswith(b"data:application/octet-stream;base64,"))
converter = Field("image_1920")
main_company = self.env.ref("base.main_company").partner_id
values = converter.message_to_odoo(self.env, "update", data_uri, partner, True)
main_company.write(values)
value = converter.odoo_to_message(main_company)
self.assertTrue(value.startswith(b"data:image/png;base64,"))
# test on an attachment field
data_uri = (
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElE"
"QVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
)
menu = self.env["ir.ui.menu"].search([], limit=1)
old_image = menu.web_icon_data
converter = Field("web_icon_data")
values = converter.message_to_odoo(self.env, "update", data_uri, menu, True)
menu.write(values)
self.assertEqual(old_image, menu.web_icon_data)
converter.post_hook(menu, data_uri)
# TODO fix that, post_hook is not working
self.assertNotEqual(old_image, menu.web_icon_data)
value = converter.odoo_to_message(menu)
self.assertTrue(value.startswith(b"data:image/png;base64,"))
# TODO test with a image/svg and a non admin user
# TODO add more tests like text/plain
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment