feat: implement server-side image compression for sign image fields.

This commit is contained in:
Suherdy Yacob 2026-01-07 15:43:18 +07:00
parent e7c8c09ca3
commit 74647b5a7b
12 changed files with 71 additions and 0 deletions

1
__init__.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import models from . import models
from . import controllers

0
__manifest__.py Normal file → Executable file
View File

Binary file not shown.

2
controllers/__init__.py Normal file
View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import main

68
controllers/main.py Normal file
View File

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
import base64
import io
import logging
from PIL import Image
from odoo import http
from odoo.http import request
from odoo.tools import image_process
from odoo.addons.sign.controllers.main import Sign
_logger = logging.getLogger(__name__)
class SignController(Sign):
@http.route(['/sign/sign/<int:request_id>/<token>'], type='json', auth='public')
def sign_document(self, request_id, token, signature=None, items=None, **kwargs):
if items:
# Filter items that look like images (base64)
# We can't easily know the type of item just from the ID without a lookup
# But we can try to decode and compress if it looks like a large base64 string
# Optimization: Fetch all item types at once to identify image field types
item_ids = [int(k) for k in items.keys()]
sign_items = request.env['sign.item'].sudo().browse(item_ids)
image_items_ids = set(sign_items.filtered(lambda i: i.type_id.item_type == 'image').ids)
for key, value in items.items():
if int(key) in image_items_ids and value and isinstance(value, str):
if value.startswith('/sign/image/'):
# Skip URLs (handled by sign_sequence_field or already processed)
continue
try:
# Value comes as "data:image/png;base64,..." usually
# But Odoo sometimes strips header or not.
# sign_image_upload.js sends the full data URL.
header = None
image_data = value
if ',' in value:
header, image_data = value.split(',', 1)
# Compress
# Decode
image_bytes = base64.b64decode(image_data)
# Process image: limit size to max 1920x1920 and reasonable quality
# image_process returns the processed image binary
processed_image = image_process(image_bytes, size=(1920, 1920), quality=80, output_format='JPEG')
# Re-encode
new_base64 = base64.b64encode(processed_image).decode('utf-8')
# Re-attach header if it was there, but ensure it matches new format (JPEG)
# Actually, keeping original header type might be misleading if we converted to JPEG.
# But for Odoo chatter/display, usually data:image/jpeg;base64 is safer if we converted.
new_value = f"data:image/jpeg;base64,{new_base64}"
items[key] = new_value
_logger.info(f"Compressed image for item {key}: {len(value)} -> {len(new_value)} chars")
except Exception as e:
_logger.warning(f"Failed to compress image for item {key}: {e}")
# Leave original value if compression fails
return super().sign_document(request_id, token, signature=signature, items=items, **kwargs)

0
data/sign_item_type_data.xml Normal file → Executable file
View File

0
models/__init__.py Normal file → Executable file
View File

0
models/sign_item_type.py Normal file → Executable file
View File

0
static/src/js/sign_image_upload.js Normal file → Executable file
View File

0
static/src/xml/sign_items_image.xml Normal file → Executable file
View File